-
-
Notifications
You must be signed in to change notification settings - Fork 740
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
feat: map project owners to projects list #6928
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,10 +4,53 @@ import { type IUser, RoleName, type IGroup } from '../../types'; | |
import { randomId } from '../../util'; | ||
import { ProjectOwnersReadModel } from './project-owners-read-model'; | ||
|
||
const mockProjectWithCounts = (name: string) => ({ | ||
name, | ||
id: name, | ||
description: '', | ||
featureCount: 0, | ||
memberCount: 0, | ||
mode: 'open' as const, | ||
defaultStickiness: 'default' as const, | ||
staleFeatureCount: 0, | ||
potentiallyStaleFeatureCount: 0, | ||
avgTimeToProduction: 0, | ||
}); | ||
|
||
describe('unit tests', () => { | ||
test('maps owners to projects', () => {}); | ||
test('maps owners to projects', () => { | ||
const projects = [{ name: 'project1' }, { name: 'project2' }] as any; | ||
|
||
const owners = { | ||
project1: [{ ownerType: 'user' as const, name: 'Owner Name' }], | ||
project2: [{ ownerType: 'user' as const, name: 'Owner Name' }], | ||
}; | ||
|
||
const projectsWithOwners = ProjectOwnersReadModel.addOwnerData( | ||
projects, | ||
owners, | ||
); | ||
|
||
expect(projectsWithOwners).toMatchObject([ | ||
{ name: 'project1', owners: [{ name: 'Owner Name' }] }, | ||
{ name: 'project2', owners: [{ name: 'Owner Name' }] }, | ||
]); | ||
}); | ||
|
||
test('returns "system" when a project has no owners', async () => { | ||
// this is a mapping test; not an integration test | ||
const projects = [{ name: 'project1' }, { name: 'project2' }] as any; | ||
|
||
const owners = {}; | ||
|
||
const projectsWithOwners = ProjectOwnersReadModel.addOwnerData( | ||
projects, | ||
owners, | ||
); | ||
|
||
expect(projectsWithOwners).toMatchObject([ | ||
{ name: 'project1', owners: [{ ownerType: 'system' }] }, | ||
{ name: 'project2', owners: [{ ownerType: 'system' }] }, | ||
]); | ||
}); | ||
}); | ||
|
||
|
@@ -16,20 +59,28 @@ let readModel: ProjectOwnersReadModel; | |
|
||
let ownerRoleId: number; | ||
let owner: IUser; | ||
let owner2: IUser; | ||
let member: IUser; | ||
let group: IGroup; | ||
let group2: IGroup; | ||
|
||
beforeAll(async () => { | ||
db = await dbInit('project_owners_read_model_serial', getLogger); | ||
readModel = new ProjectOwnersReadModel(db.rawDatabase, db.stores.roleStore); | ||
ownerRoleId = (await db.stores.roleStore.getRoleByName(RoleName.OWNER)).id; | ||
|
||
const ownerData = { | ||
name: 'Owner User', | ||
name: 'Owner Name', | ||
username: 'owner', | ||
email: '[email protected]', | ||
imageUrl: 'image-url-1', | ||
}; | ||
const ownerData2 = { | ||
name: 'Second Owner Name', | ||
username: 'owner2', | ||
email: '[email protected]', | ||
imageUrl: 'image-url-3', | ||
}; | ||
const memberData = { | ||
name: 'Member Name', | ||
username: 'member', | ||
|
@@ -40,10 +91,13 @@ beforeAll(async () => { | |
// create users | ||
owner = await db.stores.userStore.insert(ownerData); | ||
member = await db.stores.userStore.insert(memberData); | ||
owner2 = await db.stores.userStore.insert(ownerData2); | ||
|
||
// create groups | ||
group = await db.stores.groupStore.create({ name: 'Group Name' }); | ||
await db.stores.groupStore.addUserToGroups(owner.id, [group.id]); | ||
group2 = await db.stores.groupStore.create({ name: 'Second Group Name' }); | ||
await db.stores.groupStore.addUserToGroups(member.id, [group.id]); | ||
}); | ||
|
||
afterAll(async () => { | ||
|
@@ -64,6 +118,12 @@ afterEach(async () => { | |
}); | ||
|
||
describe('integration tests', () => { | ||
test('returns an empty object if there are no projects', async () => { | ||
const owners = await readModel.getAllProjectOwners(); | ||
|
||
expect(owners).toStrictEqual({}); | ||
}); | ||
|
||
test('name takes precedence over username', async () => { | ||
const projectId = randomId(); | ||
await db.stores.projectStore.create({ id: projectId, name: projectId }); | ||
|
@@ -77,7 +137,7 @@ describe('integration tests', () => { | |
const owners = await readModel.getAllProjectOwners(); | ||
expect(owners).toMatchObject({ | ||
[projectId]: expect.arrayContaining([ | ||
expect.objectContaining({ name: 'Owner User' }), | ||
expect.objectContaining({ name: 'Owner Name' }), | ||
]), | ||
}); | ||
}); | ||
|
@@ -98,7 +158,7 @@ describe('integration tests', () => { | |
[projectId]: [ | ||
{ | ||
ownerType: 'user', | ||
name: 'Owner User', | ||
name: 'Owner Name', | ||
email: '[email protected]', | ||
imageUrl: 'image-url-1', | ||
}, | ||
|
@@ -128,7 +188,7 @@ describe('integration tests', () => { | |
const owners = await readModel.getAllProjectOwners(); | ||
|
||
expect(owners).toMatchObject({ | ||
[projectId]: [{ name: 'Owner User' }], | ||
[projectId]: [{ name: 'Owner Name' }], | ||
}); | ||
}); | ||
|
||
|
@@ -155,21 +215,134 @@ describe('integration tests', () => { | |
}); | ||
}); | ||
|
||
test('users are listed before groups', async () => {}); | ||
test('users are listed before groups', async () => { | ||
const projectId = randomId(); | ||
await db.stores.projectStore.create({ id: projectId, name: projectId }); | ||
|
||
test('owners (users and groups) are sorted by when they were added; oldest first', async () => {}); | ||
await db.stores.accessStore.addGroupToRole( | ||
group.id, | ||
ownerRoleId, | ||
'', | ||
projectId, | ||
); | ||
|
||
test('returns the system owner for the default project', async () => {}); | ||
await db.stores.accessStore.addUserToRole( | ||
owner.id, | ||
ownerRoleId, | ||
projectId, | ||
); | ||
|
||
test('returns an empty list if there are no projects', async () => { | ||
const owners = await readModel.getAllProjectOwners(); | ||
|
||
expect(owners).toStrictEqual({}); | ||
expect(owners).toMatchObject({ | ||
[projectId]: [ | ||
{ | ||
email: '[email protected]', | ||
imageUrl: 'image-url-1', | ||
name: 'Owner Name', | ||
ownerType: 'user', | ||
}, | ||
{ | ||
name: 'Group Name', | ||
ownerType: 'group', | ||
}, | ||
], | ||
}); | ||
}); | ||
|
||
test('enriches fully', async () => { | ||
const owners = await readModel.enrichWithOwners([]); | ||
test('owners (users and groups) are sorted by when they were added; oldest first', async () => { | ||
const projectId = randomId(); | ||
await db.stores.projectStore.create({ id: projectId, name: projectId }); | ||
|
||
// Raw query in order to set the created_at date | ||
await db.rawDatabase('role_user').insert({ | ||
user_id: owner2.id, | ||
role_id: ownerRoleId, | ||
project: projectId, | ||
created_at: new Date('2024-01-01T00:00:00.000Z'), | ||
}); | ||
|
||
// Raw query in order to set the created_at date | ||
await db.rawDatabase('group_role').insert({ | ||
group_id: group2.id, | ||
role_id: ownerRoleId, | ||
project: projectId, | ||
created_at: new Date('2024-01-01T00:00:00.000Z'), | ||
}); | ||
|
||
await db.stores.accessStore.addGroupToRole( | ||
group.id, | ||
ownerRoleId, | ||
'', | ||
projectId, | ||
); | ||
|
||
await db.stores.accessStore.addUserToRole( | ||
owner.id, | ||
ownerRoleId, | ||
projectId, | ||
); | ||
|
||
const owners = await readModel.getAllProjectOwners(); | ||
|
||
expect(owners).toMatchObject({ | ||
[projectId]: [ | ||
{ | ||
email: '[email protected]', | ||
imageUrl: 'image-url-3', | ||
name: 'Second Owner Name', | ||
ownerType: 'user', | ||
}, | ||
{ | ||
email: '[email protected]', | ||
imageUrl: 'image-url-1', | ||
name: 'Owner Name', | ||
ownerType: 'user', | ||
}, | ||
{ | ||
name: 'Second Group Name', | ||
ownerType: 'group', | ||
}, | ||
{ | ||
name: 'Group Name', | ||
ownerType: 'group', | ||
}, | ||
], | ||
}); | ||
}); | ||
|
||
test('does not modify an empty array', async () => { | ||
const projectsWithOwners = await readModel.addOwners([]); | ||
|
||
expect(projectsWithOwners).toStrictEqual([]); | ||
}); | ||
|
||
test('adds system owner when no owners are found', async () => { | ||
const projectIdA = randomId(); | ||
const projectIdB = randomId(); | ||
await db.stores.projectStore.create({ | ||
id: projectIdA, | ||
name: projectIdA, | ||
}); | ||
await db.stores.projectStore.create({ | ||
id: projectIdB, | ||
name: projectIdB, | ||
}); | ||
|
||
await db.stores.accessStore.addUserToRole( | ||
owner.id, | ||
ownerRoleId, | ||
projectIdB, | ||
); | ||
|
||
const projectsWithOwners = await readModel.addOwners([ | ||
mockProjectWithCounts(projectIdA), | ||
mockProjectWithCounts(projectIdB), | ||
]); | ||
|
||
expect(owners).toStrictEqual([]); | ||
expect(projectsWithOwners).toMatchObject([ | ||
{ name: projectIdA, owners: [{ ownerType: 'system' }] }, | ||
{ name: projectIdB, owners: [{ ownerType: 'user' }] }, | ||
]); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Can be done later, but I think we should put the unit tests in a separate file? So we'll probably have
project-owners-read-model.test.ts
andproject-owners-read-model.integration.test.ts
. Feels like it jives better with what we're doing most the time.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.
We have 3 types that correlate better to methods under test then to any specific test type. I think we can just describe what part of the class we are testing instead of moving it to a separate file
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.
Fair enough 💁🏼