Skip to content

Commit

Permalink
[FEATURE] Creer un script pour partager les attestations précédemment…
Browse files Browse the repository at this point in the history
… obtenues (PIX-15888)

 #11020
  • Loading branch information
pix-service-auto-merge authored Jan 22, 2025
2 parents 3d91347 + ef79892 commit 573c8ac
Show file tree
Hide file tree
Showing 3 changed files with 216 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import _ from 'lodash';

import { ORGANIZATIONS_PROFILE_REWARDS_TABLE_NAME } from '../../migrations/20241118134739_create-organizations-profile-rewards-table.js';
import { databaseBuffer } from '../database-buffer.js';
import { buildOrganization } from './build-organization.js';
import { buildProfileReward } from './build-profile-reward.js';

export const buildOrganizationProfileReward = ({
id = databaseBuffer.getNextId(),
organizationId,
profileRewardId,
} = {}) => {
organizationId = _.isUndefined(organizationId) ? buildOrganization().id : organizationId;
profileRewardId = _.isUndefined(profileRewardId) ? buildProfileReward().id : profileRewardId;

const values = {
id,
organizationId,
profileRewardId,
};

return databaseBuffer.pushInsertable({
tableName: ORGANIZATIONS_PROFILE_REWARDS_TABLE_NAME,
values,
});
};
92 changes: 92 additions & 0 deletions api/src/profile/scripts/sixth-grade-organization-share.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { ORGANIZATIONS_PROFILE_REWARDS_TABLE_NAME } from '../../../db/migrations/20241118134739_create-organizations-profile-rewards-table.js';
import { PGSQL_UNIQUE_CONSTRAINT_VIOLATION_ERROR } from '../../../db/pgsql-errors.js';
import { Script } from '../../shared/application/scripts/script.js';
import { ScriptRunner } from '../../shared/application/scripts/script-runner.js';
import { DomainTransaction } from '../../shared/domain/DomainTransaction.js';

const options = {
limit: {
type: 'number',
describe: 'Id limit',
demandOption: true,
requiresArg: true,
coerce: Number,
},
offset: {
type: 'number',
describe: 'Id offset',
demandOption: true,
requiresArg: true,
coerce: Number,
},
};

export class SixthGradeOrganizationShare extends Script {
constructor() {
super({
description: 'Insert share attestations with organizations for sixth graders',
permanent: false,
options,
});
}

async handle({ options, logger }) {
const profileRewards = await this.fetchProfileRewards(options.limit, options.offset);

logger.info(`${profileRewards.length} users to handle`);

let count = 1;

for (const profileReward of profileRewards) {
logger.info(`Handling user ${profileReward.userId}: (${count}/${profileRewards.length})`);

const userOrganizationIds = await this.fetchUserOrganizations(profileReward.userId);

logger.info(`Organization ids for user ${profileReward.userId}: ${userOrganizationIds.join(',')}`);

for (const organizationId of userOrganizationIds) {
const knexConnection = await DomainTransaction.getConnection();
logger.info(`Table insertion for user ${profileReward.userId} and organization ${organizationId}`);

try {
await knexConnection(ORGANIZATIONS_PROFILE_REWARDS_TABLE_NAME).insert({
profileRewardId: profileReward.id,
organizationId,
});
} catch (error) {
if (error.code === PGSQL_UNIQUE_CONSTRAINT_VIOLATION_ERROR) {
logger.warn(
`User ${profileReward.userId} already shared an attestation with organization ${organizationId}`,
);
} else {
throw error;
}
}
}

count++;
}
}

/**
* @param {number} limit
* @param {number} offset
*
* @returns {Promise<[{id:number, userId:number}]>}
*/
async fetchProfileRewards(limit, offset) {
const knexConnection = DomainTransaction.getConnection();
return await knexConnection('profile-rewards').select('userId', 'id').limit(limit).offset(offset);
}

async fetchUserOrganizations(userId) {
const knexConnection = DomainTransaction.getConnection();
const organizations = await knexConnection('view-active-organization-learners')
.select('organizationId')
.where({ userId });

return organizations.map(({ organizationId }) => organizationId);
}
}

await ScriptRunner.execute(import.meta.url, SixthGradeOrganizationShare);
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import sinon from 'sinon';

import { SixthGradeOrganizationShare } from '../../../../src/profile/scripts/sixth-grade-organization-share.js';
import { databaseBuilder, expect, knex } from '../../../test-helper.js';

describe('Integration | Profile | Scripts | sixth-grade-organization-share ', function () {
describe('#handle', function () {
let organizationProfileRewards;
let logger;
let profileRewardIds;
let firstOrganizationId;
let secondOrganizationId;

before(async function () {
// build attestation
const attestation = databaseBuilder.factory.buildAttestation();

// build users
const userIds = [...Array(8).keys()].map((id) => databaseBuilder.factory.buildUser({ id: id + 1 }).id);

// build organizations
firstOrganizationId = databaseBuilder.factory.buildOrganization().id;
secondOrganizationId = databaseBuilder.factory.buildOrganization().id;

// build organization learners
userIds.forEach((userId) =>
databaseBuilder.factory.buildOrganizationLearner({ organizationId: firstOrganizationId, userId }),
);

// build an other organization learner for userId 3
databaseBuilder.factory.buildOrganizationLearner({ organizationId: secondOrganizationId, userId: 3 });

// build profile rewards
profileRewardIds = userIds.map(
(userId) => databaseBuilder.factory.buildProfileReward({ rewardId: attestation.id, userId }).id,
);

// build one organization profile reward to test unique constraint violation
databaseBuilder.factory.buildOrganizationProfileReward({
profileRewardId: profileRewardIds[4],
organizationId: firstOrganizationId,
});

await databaseBuilder.commit();

const script = new SixthGradeOrganizationShare();
logger = { info: sinon.spy(), warn: sinon.spy() };

await script.handle({
options: {
limit: 5,
offset: 2,
},
logger,
});

organizationProfileRewards = await knex('organizations-profile-rewards').select('*');
});

it('should handle offset option', async function () {
const organizationProfilRewardIds = organizationProfileRewards.map(
(profileReward) => profileReward.profileRewardId,
);
expect(organizationProfilRewardIds).to.not.contains(profileRewardIds[0]);
expect(organizationProfilRewardIds).to.not.contains(profileRewardIds[1]);
});

it('should handle limit option', async function () {
const organizationProfilRewardIds = organizationProfileRewards.map(
(profileReward) => profileReward.profileRewardId,
);
expect(organizationProfilRewardIds).to.not.contains(profileRewardIds[7]);
expect(organizationProfilRewardIds).to.not.contains(profileRewardIds[8]);
});

it('should handle pgsql unique constraint violation error', async function () {
expect(logger.warn).to.have.been.calledOnceWithExactly(
`User 5 already shared an attestation with organization ${firstOrganizationId}`,
);
});

it('should insert expected data', async function () {
const organizationProfileRewardsWithoutIds = organizationProfileRewards.map((organizationProfileReward) => {
delete organizationProfileReward.id;
return organizationProfileReward;
});

expect(organizationProfileRewardsWithoutIds).to.have.deep.members([
{ organizationId: firstOrganizationId, profileRewardId: profileRewardIds[2] },
{ organizationId: secondOrganizationId, profileRewardId: profileRewardIds[2] },
{ organizationId: firstOrganizationId, profileRewardId: profileRewardIds[3] },
{ organizationId: firstOrganizationId, profileRewardId: profileRewardIds[4] },
{ organizationId: firstOrganizationId, profileRewardId: profileRewardIds[5] },
{ organizationId: firstOrganizationId, profileRewardId: profileRewardIds[6] },
]);
});
});
});

0 comments on commit 573c8ac

Please sign in to comment.