-
Notifications
You must be signed in to change notification settings - Fork 51
Permissions hotfix #1743
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Permissions hotfix #1743
Changes from all commits
7beacfa
5b77f46
cc45254
b6707c4
610dbbc
0ea8942
31ec80c
0fc1caf
87edd80
8cb07c3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -32,12 +32,13 @@ jest.mock('react-helmet', () => ({ | |
| jest.mock('../../util/tc', () => ({ | ||
| checkAdmin: jest.fn(), | ||
| checkReadOnlyRoles: jest.fn(), | ||
| checkAdminOrCopilotOrManager: jest.fn(), | ||
| checkCanManageProject: jest.fn(), | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [❗❗ |
||
| checkCanViewProjectAssets: jest.fn(), | ||
| checkManager: jest.fn(), | ||
| getProjectMemberByUserId: jest.fn((project, userId) => { | ||
| getProjectMemberRole: jest.fn((project, userId) => { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [❗❗ |
||
| const members = (project && project.members) || [] | ||
| return members.find(member => `${member.userId}` === `${userId}`) || null | ||
| const member = members.find(candidate => `${candidate.userId}` === `${userId}`) || null | ||
| return member ? member.role : null | ||
| }) | ||
| })) | ||
|
|
||
|
|
@@ -111,7 +112,7 @@ describe('ChallengesComponent', () => { | |
|
|
||
| tcUtils.checkAdmin.mockReturnValue(false) | ||
| tcUtils.checkReadOnlyRoles.mockReturnValue(false) | ||
| tcUtils.checkAdminOrCopilotOrManager.mockReturnValue(false) | ||
| tcUtils.checkCanManageProject.mockReturnValue(false) | ||
| tcUtils.checkCanViewProjectAssets.mockReturnValue(true) | ||
| tcUtils.checkManager.mockReturnValue(false) | ||
| }) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| /* global describe, it, expect, beforeEach, afterEach, jest */ | ||
|
|
||
| import React from 'react' | ||
| import ReactDOM from 'react-dom' | ||
| import { act } from 'react-dom/test-utils' | ||
| import UpdateBillingAccount from './index' | ||
|
|
||
| jest.mock('../Select', () => () => null) | ||
| jest.mock('../Buttons', () => { | ||
| const React = require('react') | ||
|
|
||
| const renderButton = (text) => React.createElement( | ||
| 'button', | ||
| { type: 'button' }, | ||
| text | ||
| ) | ||
|
|
||
| return { | ||
| PrimaryButton: ({ text }) => renderButton(text), | ||
| OutlineButton: ({ text }) => renderButton(text) | ||
| } | ||
| }) | ||
|
|
||
| describe('UpdateBillingAccount', () => { | ||
| let container | ||
|
|
||
| const defaultProps = { | ||
| billingAccounts: [], | ||
| isBillingAccountsLoading: false, | ||
| isBillingAccountLoading: false, | ||
| isBillingAccountLoadingFailed: false, | ||
| billingStartDate: 'Jan 01, 2026', | ||
| billingEndDate: 'Dec 31, 2026', | ||
| isBillingAccountExpired: false, | ||
| canManageBillingAccount: false, | ||
| currentBillingAccount: null, | ||
| projectId: 1001, | ||
| updateProject: () => {} | ||
| } | ||
|
|
||
| const renderComponent = (props = {}) => { | ||
| act(() => { | ||
| ReactDOM.render( | ||
| <UpdateBillingAccount {...defaultProps} {...props} />, | ||
| container | ||
| ) | ||
| }) | ||
| } | ||
|
|
||
| beforeEach(() => { | ||
| container = document.createElement('div') | ||
| document.body.appendChild(container) | ||
| }) | ||
|
|
||
| afterEach(() => { | ||
| ReactDOM.unmountComponentAtNode(container) | ||
| container.remove() | ||
| container = null | ||
| }) | ||
|
|
||
| it('shows the select action when the user can manage billing accounts and none is assigned', () => { | ||
| renderComponent({ | ||
| canManageBillingAccount: true, | ||
| isBillingAccountLoadingFailed: true | ||
| }) | ||
|
|
||
| expect(container.textContent).toContain('No Billing Account set') | ||
| expect(container.textContent).toContain('Select Billing Account') | ||
| }) | ||
|
|
||
| it('shows the edit action when the user can manage an assigned billing account', () => { | ||
| renderComponent({ | ||
| canManageBillingAccount: true, | ||
| currentBillingAccount: 12345 | ||
| }) | ||
|
|
||
| expect(container.textContent).toContain('Billing Account:') | ||
| expect(container.textContent).toContain('Edit Billing Account') | ||
| }) | ||
|
|
||
| it('hides management actions when the user cannot manage billing accounts', () => { | ||
| renderComponent({ | ||
| currentBillingAccount: 12345 | ||
| }) | ||
|
|
||
| expect(container.textContent).toContain('Billing Account:') | ||
| expect(container.textContent).not.toContain('Edit Billing Account') | ||
| expect(container.textContent).not.toContain('Select Billing Account') | ||
| }) | ||
| }) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| /** | ||
| * Returns project details only when they match the current project context. | ||
| * | ||
| * This prevents stale project data from rendering on another project page while | ||
| * still handling APIs that may return ids as strings. | ||
| * | ||
| * @param {Object} projectDetail Loaded project details from redux. | ||
| * @param {string|number} projectId Project id from the current route. | ||
| * @param {number} activeProjectId Active project id stored in the sidebar state. | ||
| * @returns {Object} The matching project detail object, or an empty object. | ||
| */ | ||
| export function getActiveProject (projectDetail, projectId, activeProjectId) { | ||
| if (!projectDetail) { | ||
| return {} | ||
| } | ||
|
|
||
| const scopedProjectId = projectId != null | ||
| ? `${projectId}` | ||
| : (activeProjectId != null && activeProjectId !== -1 ? `${activeProjectId}` : '') | ||
|
|
||
| if (!scopedProjectId || `${projectDetail.id}` !== scopedProjectId) { | ||
| return {} | ||
| } | ||
|
|
||
| return projectDetail | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| /* global describe, it, expect */ | ||
|
|
||
| import { getActiveProject } from './helper' | ||
|
|
||
| describe('getActiveProject', () => { | ||
| it('returns the project detail when the route project id matches a string project id', () => { | ||
| const projectDetail = { | ||
| id: '100566', | ||
| name: 'Project Phoenix' | ||
| } | ||
|
|
||
| expect(getActiveProject(projectDetail, 100566, 100566)).toEqual(projectDetail) | ||
| }) | ||
|
|
||
| it('returns an empty object when the loaded project does not match the current project context', () => { | ||
| const projectDetail = { | ||
| id: '100566', | ||
| name: 'Project Phoenix' | ||
| } | ||
|
|
||
| expect(getActiveProject(projectDetail, 100567, 100567)).toEqual({}) | ||
| }) | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[⚠️
maintainability]The
PROJECT_API_URLis set to use the development API host (DEV_API_HOSTNAME). Consider using the local endpointLOCAL_PROJECTS_APIfor consistency with other local services, unless there's a specific reason to keep it on the dev environment.