Skip to content

Commit

Permalink
fix organization-membership tests
Browse files Browse the repository at this point in the history
Change-type: patch
  • Loading branch information
myarmolinsky committed Jan 15, 2025
1 parent a32d551 commit bfdc5d5
Show file tree
Hide file tree
Showing 2 changed files with 151 additions and 72 deletions.
207 changes: 137 additions & 70 deletions tests/integration/models/organization-membership.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
credentials,
givenInitialOrganization,
givenLoggedInUser,
TEST_ORGANIZATION_NAME,
} from '../setup';
import type * as BalenaSdk from '../../..';
import { assertDeepMatchAndLength, timeSuite } from '../../util';
Expand Down Expand Up @@ -64,18 +63,28 @@ describe('Organization Membership Model', function () {
});

describe('balena.models.organization.membership.getAllByOrganization()', function () {
it(`should return only the user's own membership`, async function () {
it(`should return the members of the organization`, async function () {
const opts = {
$expand: {
user: {
$select: 'username',
},
},
} satisfies BalenaSdk.PineOptions<BalenaSdk.OrganizationMembership>;
const memberships =
await balena.models.organization.membership.getAllByOrganization(
(await balena.models.organization.membership.getAllByOrganization(
this.initialOrg.id,
);
assertDeepMatchAndLength(memberships, [
{
user: { __id: this.userId },
is_member_of__organization: { __id: this.initialOrg.id },
organization_membership_role: { __id: this.orgAdminRole.id },
},
]);
opts,
)) as Array<
BalenaSdk.PineTypedResult<
BalenaSdk.OrganizationMembership,
typeof opts
>
>;
assertDeepMatchAndLength(
memberships.map((m) => m.user[0].username).sort(),
[credentials.username, credentials.member.username].sort(),
);
});
});

Expand All @@ -85,6 +94,16 @@ describe('Organization Membership Model', function () {
membership = (
await balena.models.organization.membership.getAllByOrganization(
this.initialOrg.id,
{
$filter: {
user: {
$any: {
$alias: 'u',
$expr: { u: { username: credentials.username } },
},
},
},
},
)
)[0];
});
Expand Down Expand Up @@ -126,22 +145,6 @@ describe('Organization Membership Model', function () {
});
});

describe('balena.models.organization.membership.getAllByOrganization()', function () {
it(`should return only the user's own membership`, async function () {
const memberships =
await balena.models.organization.membership.getAllByOrganization(
this.initialOrg.id,
);
assertDeepMatchAndLength(memberships, [
{
user: { __id: this.userId },
is_member_of__organization: { __id: this.initialOrg.id },
organization_membership_role: { __id: this.orgAdminRole.id },
},
]);
});
});

parallel(
'balena.models.organization.membership.getAllByUser()',
function () {
Expand Down Expand Up @@ -172,14 +175,30 @@ describe('Organization Membership Model', function () {
resource: 'organization_membership',
options: {
$filter: {
user: { $ne: this.userId },
$not: {
user: {
$any: {
$alias: 'u',
$expr: {
u: {
username: {
$in: [
credentials.username,
credentials.member.username,
],
},
},
},
},
},
},
is_member_of__organization: this.organization.id,
},
},
});
});

// TODO: re-add this test in the future, we need to add a second user that is a member of the organization from the start
// TODO: re-add this test in the future, we need a way to accept email invites in order to add and remove a user during testing
describe.skip('given a member organization membership [contained scenario]', function () {
let membership:
| BalenaSdk.PinePostResult<BalenaSdk.OrganizationMembership>
Expand All @@ -202,28 +221,34 @@ describe('Organization Membership Model', function () {
});
});

// TODO: re-add this test in the future, we need to add a second user that is a member of the organization from the start
describe.skip('given an organization with an administrator organization membership [contained scenario]', function () {
const testOrg1Name = `${TEST_ORGANIZATION_NAME}_org_member_tests_${Date.now()}`;
let testOrg: BalenaSdk.PinePostResult<BalenaSdk.Organization> | undefined;
let membership:
describe('given an organization with two organization memberships [contained scenario]', function () {
let memberMembership:
| BalenaSdk.OrganizationMembership
| BalenaSdk.PinePostResult<BalenaSdk.OrganizationMembership>
| undefined;
before(async function () {
testOrg = await balena.models.organization.create({
name: testOrg1Name,
});
});

after(async function () {
await balena.models.organization.remove(testOrg!.id);
before(async function () {
memberMembership = (
await balena.models.organization.membership.getAllByOrganization(
this.initialOrg.id,
{
$filter: {
user: {
$any: {
$alias: 'u',
$expr: { u: { username: credentials.member.username } },
},
},
},
},
)
)[0];
});

describe('balena.models.organization.membership.changeRole()', function () {
it(`should not be able to change an organization membership to an unknown role`, async function () {
const promise = balena.models.organization.membership.changeRole(
membership!.id,
memberMembership!.id,
'unknown role',
);
await expect(promise).to.be.rejected.and.eventually.have.property(
Expand All @@ -237,14 +262,14 @@ describe('Organization Membership Model', function () {
[title, keyGetter]: (typeof keyAlternatives)[number],
) => {
it(`should be able to change an organization membership to "${rolenName}" by ${title}`, async function () {
const key = keyGetter(membership!);
const key = keyGetter(memberMembership!);
await balena.models.organization.membership.changeRole(
key,
rolenName,
);

membership = await balena.models.organization.membership.get(
membership!.id,
memberMembership = await balena.models.organization.membership.get(
memberMembership!.id,
{
$select: 'id',
$expand: {
Expand All @@ -260,15 +285,15 @@ describe('Organization Membership Model', function () {
},
},
);
expect(membership).to.have.nested.property(
expect(memberMembership).to.have.nested.property(
'user[0].username',
credentials.member.username,
);
expect(membership).to.have.nested.property(
expect(memberMembership).to.have.nested.property(
'is_member_of__organization[0].id',
testOrg!.id,
this.initialOrg.id,
);
expect(membership).to.have.nested.property(
expect(memberMembership).to.have.nested.property(
'organization_membership_role[0].name',
rolenName,
);
Expand All @@ -282,39 +307,81 @@ describe('Organization Membership Model', function () {
});

describe('balena.models.organization.membership.remove()', function () {
it(`should be able to remove an administrator`, async function () {
await balena.models.organization.membership.remove(membership!.id);
// TODO: re-add this test in the future, we need a way to accept email invites in order to add and remove a user during testing
it.skip(`should be able to remove an administrator`, async function () {
await balena.models.organization.membership.remove(
memberMembership!.id,
);

const promise = balena.models.organization.membership.get(
membership!.id,
memberMembership!.id,
);

await expect(promise).to.be.rejectedWith(
'Organization Membership not found',
);
});

it(`should not be able to remove the last membership of the organization`, async function () {
const [lastAdminMembership] =
await balena.models.organization.membership.getAllByOrganization(
testOrg!.id,
{
$select: 'id',
$filter: { user: this.userId },
},
describe('given an organization with a single administrator organization membership [contained scenario]', function () {
before(async function () {
// Make sure that there is only 1 administrator.
// That's in the before(), so that if it fails, the organization.membership.remove() is not called
await balena.models.organization.membership.changeRole(
memberMembership!.id,
'member',
);
const administratorMemberships =
await balena.models.organization.membership.getAllByOrganization(
this.initialOrg.id,
{
$select: 'id',
$expand: {
user: {
$select: 'username',
},
},
$filter: {
organization_membership_role: {
$any: {
$alias: 'omr',
$expr: {
omr: {
name: 'administrator',
},
},
},
},
},
},
);
assertDeepMatchAndLength(
administratorMemberships.map((m) => m.user[0].username),
[credentials.username],
);
});

expect(lastAdminMembership).to.be.an('object');
expect(lastAdminMembership)
.to.have.property('id')
.that.is.a('number');
it(`should not be able to remove the last administrator of the organization`, async function () {
const [lastAdminMembership] =
await balena.models.organization.membership.getAllByOrganization(
this.initialOrg.id,
{
$select: 'id',
$filter: { user: this.userId },
},
);

const promise = balena.models.organization.membership.remove(
lastAdminMembership.id,
);
await expect(promise).to.be.rejectedWith(
`It is necessary that each organization that is active, includes at least one organization membership that has an organization membership role that has a name (Auth) that is equal to "administrator"`,
);
expect(lastAdminMembership).to.be.an('object');
expect(lastAdminMembership)
.to.have.property('id')
.that.is.a('number');

const promise = balena.models.organization.membership.remove(
lastAdminMembership.id,
);
await expect(promise).to.be.rejectedWith(
`It is necessary that each organization that is active, includes at least one organization membership that has an organization membership role that has a name (Auth) that is equal to "administrator"`,
);
});
});
});
});
Expand Down
16 changes: 14 additions & 2 deletions tests/integration/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,13 +213,25 @@ export async function loginPaidUser() {
}

async function resetInitialOrganization() {
const { id: userId } = await balena.auth.getUserInfo();
const initialOrg = await getInitialOrganization();
await balena.pine.delete({
resource: 'organization_membership',
options: {
$filter: {
user: { $ne: userId },
$not: {
user: {
$any: {
$alias: 'u',
$expr: {
u: {
username: {
$in: [credentials.username, credentials.member.username],
},
},
},
},
},
},
is_member_of__organization: initialOrg.id,
},
},
Expand Down

0 comments on commit bfdc5d5

Please sign in to comment.