Skip to content
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

[FEATURE] Creer un script pour partager les attestations précédemment obtenues (PIX-15888) #11020

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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}]>}
*/
Alexandre-Monney marked this conversation as resolved.
Show resolved Hide resolved
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] },
]);
});
});
});
Loading