From cc3bc1a53e987f316a8bef3ab80d72fe24f508e6 Mon Sep 17 00:00:00 2001 From: alicegoarnisson Date: Fri, 7 Mar 2025 09:06:06 +0100 Subject: [PATCH 1/3] feat(orga): created multiple sending assessment feature each time an organization is created in admin --- .../domain/models/OrganizationForAdmin.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/api/src/organizational-entities/domain/models/OrganizationForAdmin.js b/api/src/organizational-entities/domain/models/OrganizationForAdmin.js index 5ba8ada0ab4..6bfd75f79b1 100644 --- a/api/src/organizational-entities/domain/models/OrganizationForAdmin.js +++ b/api/src/organizational-entities/domain/models/OrganizationForAdmin.js @@ -98,6 +98,12 @@ class OrganizationForAdmin { active: showNPS, params: showNPS ? { formNPSUrl: formNPSUrl } : null, }; + if (this.features[ORGANIZATION_FEATURE.MULTIPLE_SENDING_ASSESSMENT.key] === undefined) { + this.features[ORGANIZATION_FEATURE.MULTIPLE_SENDING_ASSESSMENT.key] = { + active: true, + params: null, + }; + } if (this.type === 'SCO' && this.isManagingStudents) { this.features[ORGANIZATION_FEATURE.COMPUTE_ORGANIZATION_LEARNER_CERTIFICABILITY.key] = { active: true, From dee890642b482e6638f7da98c9ce5afb78c152f0 Mon Sep 17 00:00:00 2001 From: alicegoarnisson Date: Fri, 7 Mar 2025 09:07:13 +0100 Subject: [PATCH 2/3] feat(api): added a helper to create the feature each time an organization is created in tests --- api/tests/test-helper.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/api/tests/test-helper.js b/api/tests/test-helper.js index 18755c44f22..852cc25e0eb 100644 --- a/api/tests/test-helper.js +++ b/api/tests/test-helper.js @@ -26,6 +26,7 @@ import { PIX_ADMIN } from '../src/authorization/domain/constants.js'; import * as tutorialRepository from '../src/devcomp/infrastructure/repositories/tutorial-repository.js'; import * as missionRepository from '../src/school/infrastructure/repositories/mission-repository.js'; import { config } from '../src/shared/config.js'; +import { ORGANIZATION_FEATURE } from '../src/shared/domain/constants.js'; import { Membership } from '../src/shared/domain/models/index.js'; import * as tokenService from '../src/shared/domain/services/token-service.js'; import { featureToggles } from '../src/shared/infrastructure/feature-toggles/index.js'; @@ -192,6 +193,14 @@ async function insertOrganizationUserWithRoleAdmin() { return { adminUser, organization }; } +// We insert a multiple sending feature by default for each new organization created. +// It is under feature for now because we want to be able to deactivate it when asked. +async function insertMultipleSendingFeatureForNewOrganization() { + const feature = databaseBuilder.factory.buildFeature(ORGANIZATION_FEATURE.MULTIPLE_SENDING_ASSESSMENT); + await databaseBuilder.commit(); + return feature.id; +} + // Hapi const hFake = { response(source) { @@ -348,6 +357,7 @@ export { generateValidRequestAuthorizationHeaderForApplication, hFake, HttpTestServer, + insertMultipleSendingFeatureForNewOrganization, insertOrganizationUserWithRoleAdmin, insertUserWithRoleCertif, insertUserWithRoleSuperAdmin, From 8afe58341025c219ce5ff977f99e7233c6ee6f73 Mon Sep 17 00:00:00 2001 From: alicegoarnisson Date: Fri, 7 Mar 2025 09:07:49 +0100 Subject: [PATCH 3/3] feat(api): modified tests to take into account the fact that multiplesendingassessment is now a by default feature --- .../organization-controller_test.js | 2 + .../usecases/create-organization_test.js | 3 +- ...ions-with-tags-and-target-profiles_test.js | 13 ++- .../organization.admin.route.test.js | 3 +- .../organization-for-admin.repository.test.js | 87 ++++++++++++++----- 5 files changed, 83 insertions(+), 25 deletions(-) diff --git a/api/tests/acceptance/application/organizations/organization-controller_test.js b/api/tests/acceptance/application/organizations/organization-controller_test.js index c09033b341a..70f2d690cb0 100644 --- a/api/tests/acceptance/application/organizations/organization-controller_test.js +++ b/api/tests/acceptance/application/organizations/organization-controller_test.js @@ -9,6 +9,7 @@ import { databaseBuilder, expect, generateAuthenticatedUserRequestHeaders, + insertMultipleSendingFeatureForNewOrganization, insertUserWithRoleSuperAdmin, knex, sinon, @@ -24,6 +25,7 @@ describe('Acceptance | Application | organization-controller', function () { beforeEach(async function () { server = await createServer(); await insertUserWithRoleSuperAdmin(); + await insertMultipleSendingFeatureForNewOrganization(); }); describe('POST /api/admin/organizations', function () { diff --git a/api/tests/integration/domain/usecases/create-organization_test.js b/api/tests/integration/domain/usecases/create-organization_test.js index ec5a3a8a411..387ea65404c 100644 --- a/api/tests/integration/domain/usecases/create-organization_test.js +++ b/api/tests/integration/domain/usecases/create-organization_test.js @@ -4,12 +4,13 @@ import { OrganizationForAdmin } from '../../../../src/organizational-entities/do import * as dataProtectionOfficerRepository from '../../../../src/organizational-entities/infrastructure/repositories/data-protection-officer.repository.js'; import { organizationForAdminRepository } from '../../../../src/organizational-entities/infrastructure/repositories/organization-for-admin.repository.js'; import * as schoolRepository from '../../../../src/school/infrastructure/repositories/school-repository.js'; -import { databaseBuilder, expect } from '../../../test-helper.js'; +import { databaseBuilder, expect, insertMultipleSendingFeatureForNewOrganization } from '../../../test-helper.js'; describe('Integration | UseCases | create-organization', function () { it('returns newly created organization', async function () { // given const superAdminUserId = databaseBuilder.factory.buildUser().id; + await insertMultipleSendingFeatureForNewOrganization(); await databaseBuilder.commit(); const organization = new OrganizationForAdmin({ diff --git a/api/tests/integration/domain/usecases/create-organizations-with-tags-and-target-profiles_test.js b/api/tests/integration/domain/usecases/create-organizations-with-tags-and-target-profiles_test.js index fb33567b447..7739c01ac12 100644 --- a/api/tests/integration/domain/usecases/create-organizations-with-tags-and-target-profiles_test.js +++ b/api/tests/integration/domain/usecases/create-organizations-with-tags-and-target-profiles_test.js @@ -19,12 +19,18 @@ import { Membership } from '../../../../src/shared/domain/models/Membership.js'; import * as organizationRepository from '../../../../src/shared/infrastructure/repositories/organization-repository.js'; import { organizationInvitationService } from '../../../../src/team/domain/services/organization-invitation.service.js'; import { organizationInvitationRepository } from '../../../../src/team/infrastructure/repositories/organization-invitation.repository.js'; -import { catchErr, databaseBuilder, expect, knex } from '../../../test-helper.js'; +import { + catchErr, + databaseBuilder, + expect, + insertMultipleSendingFeatureForNewOrganization, + knex, +} from '../../../test-helper.js'; const { omit } = lodash; describe('Integration | UseCases | create-organizations-with-tags-and-target-profiles', function () { - let missionFeature, oralizationFeature, importStudentsFeature, ondeImportFormat, userId; + let missionFeature, oralizationFeature, importStudentsFeature, ondeImportFormat, userId, byDefaultFeatureId; beforeEach(async function () { databaseBuilder.factory.buildFeature(ORGANIZATION_FEATURE.COMPUTE_ORGANIZATION_LEARNER_CERTIFICABILITY); @@ -34,6 +40,7 @@ describe('Integration | UseCases | create-organizations-with-tags-and-target-pro ondeImportFormat = databaseBuilder.factory.buildOrganizationLearnerImportFormat({ name: ORGANIZATION_FEATURE.LEARNER_IMPORT.FORMAT.ONDE, }); + byDefaultFeatureId = await insertMultipleSendingFeatureForNewOrganization(); userId = databaseBuilder.factory.buildUser().id; await databaseBuilder.commit(); @@ -739,7 +746,7 @@ describe('Integration | UseCases | create-organizations-with-tags-and-target-pro }); // then - const savedOrganizationFeatures = await knex('organization-features'); + const savedOrganizationFeatures = await knex('organization-features').whereNot({ featureId: byDefaultFeatureId }); expect(savedOrganizationFeatures).to.have.lengthOf(3); const organizationId = createdOrganizations[0].id; expect( diff --git a/api/tests/organizational-entities/acceptance/application/organization/organization.admin.route.test.js b/api/tests/organizational-entities/acceptance/application/organization/organization.admin.route.test.js index d7f746f6b8d..32fd25f7a73 100644 --- a/api/tests/organizational-entities/acceptance/application/organization/organization.admin.route.test.js +++ b/api/tests/organizational-entities/acceptance/application/organization/organization.admin.route.test.js @@ -7,6 +7,7 @@ import { databaseBuilder, expect, generateAuthenticatedUserRequestHeaders, + insertMultipleSendingFeatureForNewOrganization, insertUserWithRoleSuperAdmin, knex, } from '../../../../test-helper.js'; @@ -19,6 +20,7 @@ describe('Acceptance | Organizational Entities | Application | Route | Admin | O beforeEach(async function () { admin = await insertUserWithRoleSuperAdmin(); + await insertMultipleSendingFeatureForNewOrganization(); await databaseBuilder.commit(); server = await createServer(); @@ -65,7 +67,6 @@ describe('Acceptance | Organizational Entities | Application | Route | Admin | O }); const tag = databaseBuilder.factory.buildTag({ id: 7, name: 'AEFE' }); databaseBuilder.factory.buildOrganizationTag({ tagId: tag.id, organizationId: organization.id }); - databaseBuilder.factory.buildFeature(ORGANIZATION_FEATURE.MULTIPLE_SENDING_ASSESSMENT); await databaseBuilder.commit(); // when diff --git a/api/tests/organizational-entities/integration/infrastructure/repositories/organization-for-admin.repository.test.js b/api/tests/organizational-entities/integration/infrastructure/repositories/organization-for-admin.repository.test.js index 2358a5d518d..502ce40eec9 100644 --- a/api/tests/organizational-entities/integration/infrastructure/repositories/organization-for-admin.repository.test.js +++ b/api/tests/organizational-entities/integration/infrastructure/repositories/organization-for-admin.repository.test.js @@ -5,14 +5,23 @@ import { organizationForAdminRepository } from '../../../../../src/organizationa import { ORGANIZATION_FEATURE } from '../../../../../src/shared/domain/constants.js'; import { MissingAttributesError, NotFoundError } from '../../../../../src/shared/domain/errors.js'; import { OrganizationInvitation } from '../../../../../src/team/domain/models/OrganizationInvitation.js'; -import { catchErr, databaseBuilder, domainBuilder, expect, knex, sinon } from '../../../../test-helper.js'; +import { + catchErr, + databaseBuilder, + domainBuilder, + expect, + insertMultipleSendingFeatureForNewOrganization, + knex, + sinon, +} from '../../../../test-helper.js'; describe('Integration | Organizational Entities | Infrastructure | Repository | organization-for-admin', function () { - let clock; + let clock, byDefaultFeatureId; const now = new Date('2022-02-02'); - beforeEach(function () { + beforeEach(async function () { clock = sinon.useFakeTimers({ now, toFake: ['Date'] }); + await databaseBuilder.commit(); }); afterEach(function () { @@ -424,6 +433,7 @@ describe('Integration | Organizational Entities | Infrastructure | Repository | creatorLastName: 'Encieux', identityProviderForCampaigns: 'genericOidcProviderCode', features: { + [ORGANIZATION_FEATURE.MULTIPLE_SENDING_ASSESSMENT.key]: { active: true, params: null }, [ORGANIZATION_FEATURE.LEARNER_IMPORT.key]: { active: false, params: null }, }, parentOrganizationId: null, @@ -485,7 +495,6 @@ describe('Integration | Organizational Entities | Infrastructure | Repository | archivedBy: archivist.id, archivedAt, }); - databaseBuilder.factory.buildFeature(ORGANIZATION_FEATURE.MULTIPLE_SENDING_ASSESSMENT); await databaseBuilder.commit(); @@ -522,9 +531,7 @@ describe('Integration | Organizational Entities | Infrastructure | Repository | creatorFirstName: superAdminUser.firstName, creatorLastName: superAdminUser.lastName, identityProviderForCampaigns: null, - features: { - [ORGANIZATION_FEATURE.MULTIPLE_SENDING_ASSESSMENT.key]: { active: false, params: null }, - }, + features: {}, parentOrganizationId: null, parentOrganizationName: null, }); @@ -537,6 +544,8 @@ describe('Integration | Organizational Entities | Infrastructure | Repository | it('saves the given organization', async function () { // given const superAdminUserId = databaseBuilder.factory.buildUser.withRole().id; + await insertMultipleSendingFeatureForNewOrganization(); + await databaseBuilder.commit(); const organization = new OrganizationForAdmin({ @@ -568,6 +577,7 @@ describe('Integration | Organizational Entities | Infrastructure | Repository | const organizationLearnerImportOndeFormat = databaseBuilder.factory.buildOrganizationLearnerImportFormat({ name: 'ONDE', }); + byDefaultFeatureId = await insertMultipleSendingFeatureForNewOrganization(); await databaseBuilder.commit(); @@ -579,9 +589,11 @@ describe('Integration | Organizational Entities | Infrastructure | Repository | const savedOrganization = await organizationForAdminRepository.save(organization); - const savedOrganizationFeatures = await knex('organization-features').where({ - organizationId: savedOrganization.id, - }); + const savedOrganizationFeatures = await knex('organization-features') + .where({ + organizationId: savedOrganization.id, + }) + .whereNot({ featureId: byDefaultFeatureId }); expect(savedOrganizationFeatures).to.have.lengthOf(3); const savedOrganizationFeatureIds = savedOrganizationFeatures.map( @@ -603,6 +615,10 @@ describe('Integration | Organizational Entities | Infrastructure | Repository | }); describe('#update', function () { + beforeEach(async function () { + byDefaultFeatureId = await insertMultipleSendingFeatureForNewOrganization(); + }); + it('updates organization detail', async function () { // given const parentOrganizationId = databaseBuilder.factory.buildOrganization({ name: 'Parent Organization' }).id; @@ -627,8 +643,7 @@ describe('Integration | Organizational Entities | Infrastructure | Repository | name: 'super orga', createdBy: userId, }); - - const featureId = databaseBuilder.factory.buildFeature(ORGANIZATION_FEATURE.MULTIPLE_SENDING_ASSESSMENT).id; + const featureId = databaseBuilder.factory.buildFeature(ORGANIZATION_FEATURE.MISSIONS_MANAGEMENT).id; await databaseBuilder.commit(); // when @@ -637,13 +652,15 @@ describe('Integration | Organizational Entities | Infrastructure | Repository | documentationUrl: 'https://pix.fr/', features: { [ORGANIZATION_FEATURE.LEARNER_IMPORT.key]: { active: false }, - [ORGANIZATION_FEATURE.MULTIPLE_SENDING_ASSESSMENT.key]: { active: true }, + [ORGANIZATION_FEATURE.MISSIONS_MANAGEMENT.key]: { active: true }, }, }); await organizationForAdminRepository.update(organizationToUpdate); // then - const enabledFeatures = await knex('organization-features').where({ organizationId: organization.id }); + const enabledFeatures = await knex('organization-features') + .where({ organizationId: organization.id, featureId }) + .whereNot({ featureId: byDefaultFeatureId }); expect(enabledFeatures).to.have.lengthOf(1); expect(enabledFeatures[0].featureId).to.equal(featureId); }); @@ -656,7 +673,8 @@ describe('Integration | Organizational Entities | Infrastructure | Repository | createdBy: userId, }); - const featureId = databaseBuilder.factory.buildFeature(ORGANIZATION_FEATURE.MULTIPLE_SENDING_ASSESSMENT).id; + const featureId = databaseBuilder.factory.buildFeature(ORGANIZATION_FEATURE.MISSIONS_MANAGEMENT).id; + databaseBuilder.factory.buildOrganizationFeature({ organizationId: organization.id, featureId }); await databaseBuilder.commit(); @@ -665,14 +683,16 @@ describe('Integration | Organizational Entities | Infrastructure | Repository | id: organization.id, documentationUrl: 'https://pix.fr/', features: { - [ORGANIZATION_FEATURE.MULTIPLE_SENDING_ASSESSMENT.key]: { active: true }, + [ORGANIZATION_FEATURE.MISSIONS_MANAGEMENT.key]: { active: true }, }, }); await organizationForAdminRepository.update(organizationToUpdate); // then - const enabledFeatures = await knex('organization-features').where({ organizationId: organization.id }); + const enabledFeatures = await knex('organization-features') + .where({ organizationId: organization.id }) + .whereNot({ featureId: byDefaultFeatureId }); expect(enabledFeatures).to.have.lengthOf(1); expect(enabledFeatures[0].featureId).to.equal(featureId); }); @@ -690,7 +710,7 @@ describe('Integration | Organizational Entities | Infrastructure | Repository | createdBy: userId, }); - const featureId = databaseBuilder.factory.buildFeature(ORGANIZATION_FEATURE.MULTIPLE_SENDING_ASSESSMENT).id; + const featureId = databaseBuilder.factory.buildFeature(ORGANIZATION_FEATURE.MISSIONS_MANAGEMENT).id; databaseBuilder.factory.buildOrganizationFeature({ organizationId: organization.id, featureId }); databaseBuilder.factory.buildOrganizationFeature({ organizationId: otherOrganization.id, featureId }); @@ -701,17 +721,42 @@ describe('Integration | Organizational Entities | Infrastructure | Repository | id: organization.id, documentationUrl: 'https://pix.fr/', features: { - [ORGANIZATION_FEATURE.MULTIPLE_SENDING_ASSESSMENT.key]: { active: false }, + [ORGANIZATION_FEATURE.MISSIONS_MANAGEMENT.key]: { active: false }, }, }); await organizationForAdminRepository.update(organizationToUpdate); //then - const enabledFeatures = await knex('organization-features'); + const enabledFeatures = await knex('organization-features').whereNot({ featureId: byDefaultFeatureId }); expect(enabledFeatures).to.have.lengthOf(1); expect(enabledFeatures[0].organizationId).to.equal(otherOrganization.id); }); + it('should disable the "by default" feature for a given organization', async function () { + // given + const userId = databaseBuilder.factory.buildUser({ firstName: 'Anne', lastName: 'Héantie' }).id; + const organization = databaseBuilder.factory.buildOrganization({ + name: 'super orga', + createdBy: userId, + }); + + await databaseBuilder.commit(); + + // when + const organizationToUpdate = new OrganizationForAdmin({ + id: organization.id, + documentationUrl: 'https://pix.fr/', + features: { + [ORGANIZATION_FEATURE.MULTIPLE_SENDING_ASSESSMENT.key]: { active: false }, + }, + }); + await organizationForAdminRepository.update(organizationToUpdate); + + //then + const enabledFeatures = await knex('organization-features'); + expect(enabledFeatures).to.have.lengthOf(0); + }); + it('should create data protection officer', async function () { // given const userId = databaseBuilder.factory.buildUser({ firstName: 'Spider', lastName: 'Man' }).id; @@ -750,6 +795,7 @@ describe('Integration | Organizational Entities | Infrastructure | Repository | name: 'super orga', createdBy: userId, }); + databaseBuilder.factory.buildDataProtectionOfficer.withOrganizationId({ organizationId: organization.id, firstName: 'Tony', @@ -785,6 +831,7 @@ describe('Integration | Organizational Entities | Infrastructure | Repository | const organizationId = databaseBuilder.factory.buildOrganization().id; const tagId = databaseBuilder.factory.buildTag({ name: 'myTag' }).id; const otherTagId = databaseBuilder.factory.buildTag({ name: 'myOtherTag' }).id; + await databaseBuilder.commit(); const tagsToAdd = [ { tagId, organizationId },