Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -109,12 +109,10 @@ const sendInvitations = async function (request, h) {
const resendInvitation = async function (request, h) {
const organizationId = request.params.id;
const email = request.payload.data.attributes.email;
const locale = extractLocaleFromRequest(request);

const organizationInvitation = await usecases.resendOrganizationInvitation({
organizationId,
email,
locale,
});
return h.response(organizationInvitationSerializer.serialize(organizationInvitation));
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ const createOrUpdateOrganizationInvitation = async ({
role,
locale,
});
} else {
organizationInvitation = await organizationInvitationRepository.update({
id: organizationInvitation.id,
...(role && { role }),
...(locale && { locale }),
});
}

const organization = await organizationRepository.get(organizationId);
Expand All @@ -67,8 +73,7 @@ const createOrUpdateOrganizationInvitation = async ({

throw new SendingEmailError(email);
}

return await organizationInvitationRepository.updateModificationDate(organizationInvitation.id);
return organizationInvitation;
};

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
const resendOrganizationInvitation = async function ({
organizationId,
email,
locale,
organizationId,
organizationRepository,
organizationInvitationRepository,
organizationInvitationService,
}) {
return organizationInvitationService.createOrUpdateOrganizationInvitation({
email,
organizationId,
organizationRepository,
organizationInvitationRepository,
organizationId,
email,
locale,
});
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,25 @@ const updateModificationDate = async function (id) {
return new OrganizationInvitation(organizationInvitation);
};

/**
* @param organizationInvitation
* @returns {Promise<OrganizationInvitation>}
*/
const update = async function (organizationInvitation) {
const [updatedOrganizationInvitation] = await knex('organization-invitations')
.where({ id: organizationInvitation.id })
.update({
...organizationInvitation,
updatedAt: new Date(),
})
.returning('*');

if (!updatedOrganizationInvitation) {
throw new NotFoundError(`Organization invitation of id ${organizationInvitation.id} is not found.`);
}
return new OrganizationInvitation(updatedOrganizationInvitation);
};

export const organizationInvitationRepository = {
create,
findOnePendingByOrganizationIdAndEmail,
Expand All @@ -135,4 +154,5 @@ export const organizationInvitationRepository = {
markAsAccepted,
markAsCancelled,
updateModificationDate,
update,
};
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { organizationInvitationService } from '../../../../../src/team/domain/se
import { organizationInvitationRepository } from '../../../../../src/team/infrastructure/repositories/organization-invitation.repository.js';
import { catchErr, databaseBuilder, expect, sinon } from '../../../../test-helper.js';

describe('Integration | Team | Domain | Service | organization-invitation', function () {
describe('Integration | Team | Domain | Service | organizationInvitationService', function () {
describe('#createOrUpdateOrganizationInvitation', function () {
let clock;
const now = new Date('2021-01-02');
Expand Down Expand Up @@ -60,27 +60,40 @@ describe('Integration | Team | Domain | Service | organization-invitation', func
);
});

it('re-sends an email with same code when organization invitation already exists with status pending', async function () {
// given
const organizationInvitation = databaseBuilder.factory.buildOrganizationInvitation({
status: OrganizationInvitation.StatusType.PENDING,
});
await databaseBuilder.commit();
context('when the organizationInvitation already exists with pending status', function () {
it('updates the organizationInvitation and re-sends an email with same code', async function () {
// given
const locale = 'fr';
const role = Membership.roles.MEMBER;
const organizationInvitation = databaseBuilder.factory.buildOrganizationInvitation({
status: OrganizationInvitation.StatusType.PENDING,
role,
locale,
});
await databaseBuilder.commit();

// when
const result = await organizationInvitationService.createOrUpdateOrganizationInvitation({
organizationId: organizationInvitation.organizationId,
email: organizationInvitation.email,
organizationRepository,
organizationInvitationRepository,
});
const newRole = Membership.roles.ADMIN;
const newLocale = 'en';

// then
const expectedOrganizationInvitation = {
...organizationInvitation,
updatedAt: now,
};
expect(_.omit(result, 'organizationName')).to.deep.equal(expectedOrganizationInvitation);
// when
const result = await organizationInvitationService.createOrUpdateOrganizationInvitation({
organizationId: organizationInvitation.organizationId,
email: organizationInvitation.email,
role: newRole,
locale: newLocale,
organizationRepository,
organizationInvitationRepository,
});

// then
const expectedOrganizationInvitation = {
...organizationInvitation,
role: newRole,
locale: newLocale,
updatedAt: now,
};
expect(_.omit(result, 'organizationName')).to.deep.equal(expectedOrganizationInvitation);
});
});

context('when recipient email has an invalid domain', function () {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Membership } from '../../../../../src/shared/domain/models/index.js';
import { OrganizationArchivedError } from '../../../../../src/team/domain/errors.js';
import { usecases } from '../../../../../src/team/domain/usecases/index.js';
import { catchErr, databaseBuilder, expect, knex } from '../../../../test-helper.js';

describe('Integration | Team | Domain | UseCase | create-organization-invitation-by-admin', function () {
it('creates an organizationInvitation', async function () {
// given
const organizationId = databaseBuilder.factory.buildOrganization().id;
await databaseBuilder.commit();

const email = '[email protected]';
const locale = 'fr-fr';
const role = Membership.roles.MEMBER;

// when
await usecases.createOrganizationInvitationByAdmin({
organizationId,
email,
locale,
role,
});

// then
const organizationInvitations = await knex('organization-invitations');
expect(organizationInvitations).to.have.lengthOf(1);
const organizationInvitation = organizationInvitations[0];
expect(organizationInvitation).to.deep.include({ email, locale, role });
});

context('when the organization is archived', function () {
it('throws an OrganizationArchivedError', async function () {
// given
const archivedBy = databaseBuilder.factory.buildUser().id;
const organizationId = databaseBuilder.factory.buildOrganization({ archivedAt: '2022-02-02', archivedBy }).id;
await databaseBuilder.commit();

const email = '[email protected]';
const locale = 'fr-fr';
const role = Membership.roles.MEMBER;

// when
const error = await catchErr(usecases.createOrganizationInvitationByAdmin)({
organizationId,
email,
locale,
role,
});

// then
expect(error).to.be.instanceOf(OrganizationArchivedError);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { OrganizationArchivedError } from '../../../../../src/team/domain/errors.js';
import { usecases } from '../../../../../src/team/domain/usecases/index.js';
import { catchErr, databaseBuilder, expect, knex } from '../../../../test-helper.js';

describe('Integration | Team | Domain | UseCase | create-organization-invitations', function () {
it('creates multiple organizationInvitations with trimmed and deduplicated emails', async function () {
// given
const organizationId = databaseBuilder.factory.buildOrganization().id;
await databaseBuilder.commit();

const emails = ['[email protected]', ' [email protected]', '[email protected]'];
const locale = 'fr-fr';

// when
await usecases.createOrganizationInvitations({
organizationId,
emails,
locale,
});

// then
const organizationInvitations = await knex('organization-invitations');
expect(organizationInvitations).to.have.lengthOf(2);
const organizationInvitationMap = Object.fromEntries(
organizationInvitations.map((organizationInvitation) => {
return [organizationInvitation.email, organizationInvitation];
}),
);
const organizationInvitation1 = organizationInvitationMap['[email protected]'];
expect(organizationInvitation1).to.deep.include({ locale, role: null });

const organizationInvitation2 = organizationInvitationMap['[email protected]'];
expect(organizationInvitation2).to.deep.include({ locale, role: null });
});

context('when the organization is archived', function () {
it('throws an OrganizationArchivedError', async function () {
// given
const archivedBy = databaseBuilder.factory.buildUser().id;
const organizationId = databaseBuilder.factory.buildOrganization({ archivedAt: '2022-02-02', archivedBy }).id;
await databaseBuilder.commit();

const emails = ['[email protected]'];
const locale = 'fr-fr';

// when
const error = await catchErr(usecases.createOrganizationInvitations)({
organizationId,
emails,
locale,
});

// then
expect(error).to.be.instanceOf(OrganizationArchivedError);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -353,4 +353,45 @@ describe('Integration | Team | Infrastructure | Repository | organization-invita
expect(error).to.be.instanceOf(NotFoundError);
});
});

describe('#update', function () {
it('updates information in organization invitation', async function () {
// given
const organizationInvitation = databaseBuilder.factory.buildOrganizationInvitation({
locale: 'en',
role: Membership.roles.MEMBER,
});
await databaseBuilder.commit();

const updatedLocale = 'fr';
const updatedRole = Membership.roles.ADMIN;

// when
const updatedOrganizationInvitation = await organizationInvitationRepository.update({
...organizationInvitation,
locale: updatedLocale,
role: updatedRole,
});

// then
expect(updatedOrganizationInvitation).to.be.instanceOf(OrganizationInvitation);
expect(updatedOrganizationInvitation.id).to.equal(organizationInvitation.id);
expect(updatedOrganizationInvitation.locale).to.equal(updatedLocale);
expect(updatedOrganizationInvitation.role).to.equal(updatedRole);
expect(updatedOrganizationInvitation.updatedAt).to.deep.equal(now);
});

it('throws an error if organization invitation is not found', async function () {
// when
const error = await catchErr(organizationInvitationRepository.update)({
id: 1234,
locale: 'fr',
role: Membership.roles.ADMIN,
});

// then
expect(error).to.be.instanceOf(NotFoundError);
expect(error.message).to.equal('Organization invitation of id 1234 is not found.');
});
});
});
Loading