From bc08511a8346968eda4048052ad3c912b7ee41f8 Mon Sep 17 00:00:00 2001 From: LomyW Date: Thu, 11 Jan 2024 08:01:32 +0100 Subject: [PATCH 01/13] add files for achievement integration test --- integration-tests/15_achievements.ts | 12 ++++++++++++ integration-tests/base/achievements.ts | 16 ++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 integration-tests/15_achievements.ts create mode 100644 integration-tests/base/achievements.ts diff --git a/integration-tests/15_achievements.ts b/integration-tests/15_achievements.ts new file mode 100644 index 000000000..43cb3c3a3 --- /dev/null +++ b/integration-tests/15_achievements.ts @@ -0,0 +1,12 @@ +import assert from 'assert'; +import { pupilOne, pupilTwo } from './01_user'; +import { prisma } from '../common/prisma'; +import { test } from './base'; + +void test('Reward Sequential Step Achievement', async () => {}); +void test('Reward Final Sequential Achievement', async () => {}); + +void test('Reward Tiered Achievement', async () => {}); + +void test('Reward New Record Streak Achievement', async () => {}); +void test('Broken Streak', async () => {}); diff --git a/integration-tests/base/achievements.ts b/integration-tests/base/achievements.ts new file mode 100644 index 000000000..b451f12b4 --- /dev/null +++ b/integration-tests/base/achievements.ts @@ -0,0 +1,16 @@ +import assert from 'assert'; +import { achievement_template_for_enum } from '../../graphql/generated'; + +interface MockAchievementTemplate { + id: number; +} + +interface MochUserAchievement { + id: number; +} + +export async function createMockSequentialTemplate(metrics: string[]) {} + +export async function createMockTieredTemplate(metrics: string[]) {} + +export async function createMockStreakTemplate(metrics: string[]) {} From 63df121e0f7b02df2393d9d57537154ca85e6980 Mon Sep 17 00:00:00 2001 From: LomyW Date: Sun, 14 Jan 2024 16:55:10 +0100 Subject: [PATCH 02/13] add draft mutations --- integration-tests/15_achievements.ts | 122 +++++++++++++++++++++++-- integration-tests/base/achievements.ts | 16 ---- integration-tests/index.ts | 1 + 3 files changed, 117 insertions(+), 22 deletions(-) delete mode 100644 integration-tests/base/achievements.ts diff --git a/integration-tests/15_achievements.ts b/integration-tests/15_achievements.ts index 43cb3c3a3..eb5b27b77 100644 --- a/integration-tests/15_achievements.ts +++ b/integration-tests/15_achievements.ts @@ -1,12 +1,122 @@ import assert from 'assert'; -import { pupilOne, pupilTwo } from './01_user'; -import { prisma } from '../common/prisma'; +import { studentOne } from './01_user'; import { test } from './base'; +import { screenedInstructorOne } from './02_screening'; +import { adminClient } from './base/clients'; +import { prisma } from '../common/prisma'; + +void test('Reward sequential step achievement', async () => { + const { client, student } = await studentOne; + + const date = new Date(); + + await adminClient.request(` + mutation verifyEmail { + _verifyEmail( + userID: "${student.userID}" + ) + } + `); + + await adminClient.request(` + mutation studentInstructorScreening { + studentInstructorScreeningCreate(studentId: ${student.student.id}, screening: { + success: true, + }) + } + + `); + + await adminClient.request(` + mutation studentCoc { + certificateOfConductCreate( + studentId: ${student.student.id}, + criminalRecords: false, + dateOfIssue: "${date}", + dateOfInspection: "${date}") + } + `); -void test('Reward Sequential Step Achievement', async () => {}); -void test('Reward Final Sequential Achievement', async () => {}); + const templates = await prisma.achievement_template.findMany({ where: { isActive: true } }); + console.log('TEMPLATES', templates); -void test('Reward Tiered Achievement', async () => {}); + const events = await prisma.achievement_event.findMany({ where: { userId: student.userID } }); + console.log('EVENTS', events); -void test('Reward New Record Streak Achievement', async () => {}); + // const { + // me: { achievements }, + // } = await client.request(` + // query achievements { + // me { + // achievements { + // id + // name + // subtitle + // description + // image + // alternativeText + // actionType + // achievementType + // achievementState + // steps { + // name + // isActive + // } + // maxSteps + // currentStep + // isNewAchievement + // progressDescription + // actionName + // actionRedirectLink + // } + // } + // } + // `); + + // console.log('ACHIEVEMENTS:', achievements); + + // assert.ok(achievements.length > 0); +}); +void test('Reward final sequential achievement', async () => {}); + +void test('Reward tiered achievement', async () => {}); + +void test('Reward new record streak achievement', async () => {}); void test('Broken Streak', async () => {}); + +void test('Get my achievements', async () => { + const { client } = await screenedInstructorOne; + + const { + me: { achievements }, + } = await client.request(` + query achievements { + me { + achievements { + id + name + subtitle + description + image + alternativeText + actionType + achievementType + achievementState + steps { + name + isActive + } + maxSteps + currentStep + isNewAchievement + progressDescription + actionName + actionRedirectLink + } + } + } + `); + + assert.ok(achievements); + return achievements; +}); diff --git a/integration-tests/base/achievements.ts b/integration-tests/base/achievements.ts deleted file mode 100644 index b451f12b4..000000000 --- a/integration-tests/base/achievements.ts +++ /dev/null @@ -1,16 +0,0 @@ -import assert from 'assert'; -import { achievement_template_for_enum } from '../../graphql/generated'; - -interface MockAchievementTemplate { - id: number; -} - -interface MochUserAchievement { - id: number; -} - -export async function createMockSequentialTemplate(metrics: string[]) {} - -export async function createMockTieredTemplate(metrics: string[]) {} - -export async function createMockStreakTemplate(metrics: string[]) {} diff --git a/integration-tests/index.ts b/integration-tests/index.ts index ff65644ad..e7ce97820 100644 --- a/integration-tests/index.ts +++ b/integration-tests/index.ts @@ -34,5 +34,6 @@ import './12_notifications'; /* Account Deactivation - Independent, but needs to be last */ import './13_deactivation'; import './14_redaction'; +import './15_achievements'; void finalizeTests(); From 180f6643451760ad12ed1c04f0b6ae90f12da1c3 Mon Sep 17 00:00:00 2001 From: Lucas Date: Thu, 18 Jan 2024 11:08:09 +0100 Subject: [PATCH 03/13] feat: reward onboarding achievement sequence test --- common/achievement/template.ts | 2 +- common/achievement/util.ts | 4 +- integration-tests/02_screening.ts | 2 +- integration-tests/15_achievements.ts | 291 +++++++++++++++++---------- 4 files changed, 192 insertions(+), 107 deletions(-) diff --git a/common/achievement/template.ts b/common/achievement/template.ts index 110a933ff..cb9958b22 100644 --- a/common/achievement/template.ts +++ b/common/achievement/template.ts @@ -16,7 +16,7 @@ export enum TemplateSelectEnum { const achievementTemplates: Map> = new Map(); async function getAchievementTemplates(select: TemplateSelectEnum): Promise> { - if (!achievementTemplates.has(select)) { + if (!achievementTemplates.has(select) || !achievementTemplates[select]) { achievementTemplates.set(select, new Map()); const templatesFromDB = await prisma.achievement_template.findMany({ diff --git a/common/achievement/util.ts b/common/achievement/util.ts index bc533076f..1cae67d6e 100644 --- a/common/achievement/util.ts +++ b/common/achievement/util.ts @@ -21,7 +21,7 @@ function getRelationTypeAndId(relation: string): [type: RelationTypes, id: strin export async function getBucketContext(myUserID: string, relation?: string): Promise { const [userType, userId] = getUserTypeAndIdForUserId(myUserID); - const whereClause = { [`${userType}Id`]: userId }; + const whereClause = {}; let relationType = null; if (relation) { @@ -35,6 +35,7 @@ export async function getBucketContext(myUserID: string, relation?: string): Pro let matches = []; if (!relationType || relationType === 'match') { + whereClause[`${userType}Id`] = userId; matches = await prisma.match.findMany({ where: whereClause, select: { @@ -46,6 +47,7 @@ export async function getBucketContext(myUserID: string, relation?: string): Pro let subcourses = []; if (!relationType || relationType === 'subcourse') { + delete whereClause[`${userType}Id`]; subcourses = await prisma.subcourse.findMany({ where: whereClause, select: { diff --git a/integration-tests/02_screening.ts b/integration-tests/02_screening.ts index 81b4c781b..0bb5e0c2d 100644 --- a/integration-tests/02_screening.ts +++ b/integration-tests/02_screening.ts @@ -4,7 +4,7 @@ import { test } from './base'; import { adminClient, createUserClient } from './base/clients'; import { instructorOne, instructorTwo, pupilOne, studentOne } from './01_user'; -const screenerOne = test('Admin can create Screener Account', async () => { +export const screenerOne = test('Admin can create Screener Account', async () => { const email = `test+${randomBytes(10).toString('base64')}@lern-fair.de`; const firstname = randomBytes(10).toString('base64'); const lastname = randomBytes(10).toString('base64'); diff --git a/integration-tests/15_achievements.ts b/integration-tests/15_achievements.ts index eb5b27b77..9dd3ea7a6 100644 --- a/integration-tests/15_achievements.ts +++ b/integration-tests/15_achievements.ts @@ -1,122 +1,205 @@ -import assert from 'assert'; -import { studentOne } from './01_user'; +import { createNewStudent } from './01_user'; import { test } from './base'; -import { screenedInstructorOne } from './02_screening'; +import { screenerOne } from './02_screening'; import { adminClient } from './base/clients'; import { prisma } from '../common/prisma'; +import { achievement_template_for_enum, achievement_type_enum, achievement_action_type_enum } from '@prisma/client'; +import { getUser } from '../common/user'; +import { getLogger } from '../common/logger/logger'; -void test('Reward sequential step achievement', async () => { - const { client, student } = await studentOne; +const logger = getLogger('Token'); - const date = new Date(); +void test('Reward onboarding achievement sequence', async () => { + await adminClient.request(`mutation ResetRateLimits { _resetRateLimits }`); + await createStudentOnboardingTemplates(); - await adminClient.request(` - mutation verifyEmail { - _verifyEmail( - userID: "${student.userID}" - ) - } - `); + // Verify Email + const { student } = await createNewStudent(); + const user = await getUser(student.userID); - await adminClient.request(` - mutation studentInstructorScreening { - studentInstructorScreeningCreate(studentId: ${student.student.id}, screening: { - success: true, - }) + const student_onboarding_1 = await prisma.user_achievement.findFirst({ + where: { + group: 'student_onboarding', + groupOrder: 1, + achievedAt: { not: null }, + userId: user.userID, + }, + }); + if (!student_onboarding_1) { + throw new Error(`There was no achievement created or found during or after the email verification process.`); } - + logger.info('The Achievement 1 for group student_onboarding was created and found when verifying a users E-Mail.'); + + // Screening + const { client: screenerClient } = await screenerOne; + await screenerClient.request(` + mutation ScreenInstructorOne { + studentTutorScreeningCreate( + studentId: ${student.student.id} + screening: {success: true comment: "" knowsCoronaSchoolFrom: ""} + ) + } `); + const student_onboarding_2 = await prisma.user_achievement.findFirst({ + where: { + group: 'student_onboarding', + groupOrder: 3, + achievedAt: { not: null }, + userId: user.userID, + }, + }); + if (!student_onboarding_2) { + throw new Error(`There was no achievement created or found during or after the screening process`); + } + logger.info('The Achievement 3 for group student_onboarding was created and found when screeing a tutor'); + // Create Certificate of Conduct + const newDate = JSON.stringify(new Date()); await adminClient.request(` - mutation studentCoc { + mutation CreateCertificateOfConduct { certificateOfConductCreate( - studentId: ${student.student.id}, - criminalRecords: false, - dateOfIssue: "${date}", - dateOfInspection: "${date}") + dateOfInspection: ${newDate}, + dateOfIssue: ${newDate}, + criminalRecords: false, + studentId: ${student.student.id}, + ) } `); - - const templates = await prisma.achievement_template.findMany({ where: { isActive: true } }); - console.log('TEMPLATES', templates); - - const events = await prisma.achievement_event.findMany({ where: { userId: student.userID } }); - console.log('EVENTS', events); - - // const { - // me: { achievements }, - // } = await client.request(` - // query achievements { - // me { - // achievements { - // id - // name - // subtitle - // description - // image - // alternativeText - // actionType - // achievementType - // achievementState - // steps { - // name - // isActive - // } - // maxSteps - // currentStep - // isNewAchievement - // progressDescription - // actionName - // actionRedirectLink - // } - // } - // } - // `); - - // console.log('ACHIEVEMENTS:', achievements); - - // assert.ok(achievements.length > 0); + const student_onboarding_3 = await prisma.user_achievement.findFirst({ + where: { + group: 'student_onboarding', + groupOrder: 4, + achievedAt: { not: null }, + userId: user.userID, + }, + }); + const student_onboarding_4 = await prisma.user_achievement.findFirst({ + where: { + group: 'student_onboarding', + groupOrder: 5, + userId: user.userID, + }, + }); + if (!student_onboarding_3) { + throw new Error(`There was no achievement created or found during or after the creation of a CoC`); + } else if (!student_onboarding_4) { + throw new Error(`There was no final achievement created or found after the creation of a CoC`); + } + logger.info('The Achievement 4 adn 5 for group student_onboarding were created and found when creating a CoC'); }); -void test('Reward final sequential achievement', async () => {}); +// void test('Reward final sequential achievement', async () => {}); -void test('Reward tiered achievement', async () => {}); +// void test('Reward tiered achievement', async () => {}); -void test('Reward new record streak achievement', async () => {}); -void test('Broken Streak', async () => {}); +// void test('Reward new record streak achievement', async () => {}); +// void test('Broken Streak', async () => {}); -void test('Get my achievements', async () => { - const { client } = await screenedInstructorOne; - - const { - me: { achievements }, - } = await client.request(` - query achievements { - me { - achievements { - id - name - subtitle - description - image - alternativeText - actionType - achievementType - achievementState - steps { - name - isActive - } - maxSteps - currentStep - isNewAchievement - progressDescription - actionName - actionRedirectLink - } - } - } - `); - - assert.ok(achievements); - return achievements; -}); +const createStudentOnboardingTemplates = async () => { + await prisma.achievement_template.create({ + data: { + name: 'Onboarding abschließen', + metrics: ['student_onboarding_verified'], + templateFor: achievement_template_for_enum.Global, + group: 'student_onboarding', + groupOrder: 1, + stepName: 'Verifizieren', + type: achievement_type_enum.SEQUENTIAL, + subtitle: 'Jetzt durchstarten', + description: 'Dieser Text muss noch geliefert werden.', + image: 'Puzzle_00', + achievedImage: '', + actionName: 'E-Mail erneut senden', + actionRedirectLink: '', + actionType: achievement_action_type_enum.Action, + condition: 'student_verified_events > 0', + conditionDataAggregations: { student_verified_events: { metric: 'student_onboarding_verified', aggregator: 'count' } }, + isActive: true, + }, + }); + await prisma.achievement_template.create({ + data: { + name: 'Onboarding abschließen', + metrics: ['student_onboarding_appointment_booked'], + templateFor: achievement_template_for_enum.Global, + group: 'student_onboarding', + groupOrder: 2, + stepName: 'Kennenlerngespräch buchen', + type: achievement_type_enum.SEQUENTIAL, + subtitle: 'Jetzt durchstarten', + description: 'Dieser Text muss noch geliefert werden.', + image: 'Puzzle_01', + achievedImage: '', + actionName: 'Termin vereinbaren', + actionRedirectLink: 'https://calendly.com', + actionType: achievement_action_type_enum.Action, + condition: 'student_appointment_booked_events > 0', + conditionDataAggregations: { + student_appointment_booked_events: { metric: 'student_onboarding_appointment_booked', aggregator: 'count' }, + }, + isActive: false, + }, + }); + await prisma.achievement_template.create({ + data: { + name: 'Onboarding abschließen', + metrics: ['student_onboarding_screened'], + templateFor: achievement_template_for_enum.Global, + group: 'student_onboarding', + groupOrder: 3, + stepName: 'Screening absolvieren', + type: achievement_type_enum.SEQUENTIAL, + subtitle: 'Jetzt durchstarten', + description: 'Dieser Text muss noch geliefert werden.', + image: 'Puzzle_02', + achievedImage: '', + actionName: 'Screening absolvieren', + actionRedirectLink: '', + actionType: achievement_action_type_enum.Appointment, + condition: 'student_screened_events > 0', + conditionDataAggregations: { student_screened_events: { metric: 'student_onboarding_screened', aggregator: 'count' } }, + isActive: true, + }, + }); + await prisma.achievement_template.create({ + data: { + name: 'Onboarding abschließen', + metrics: ['student_onboarding_coc_success'], + templateFor: achievement_template_for_enum.Global, + group: 'student_onboarding', + groupOrder: 4, + stepName: 'Führungszeugnis einreichen', + type: achievement_type_enum.SEQUENTIAL, + subtitle: 'Jetzt durchstarten', + description: 'Dieser Text muss noch geliefert werden.', + image: 'Puzzle_02', + achievedImage: '', + actionName: 'Zeugnis einreichen', + actionRedirectLink: 'mailto:fz@lern-fair.de', + actionType: achievement_action_type_enum.Action, + condition: 'student_coc_success_events > 0', + conditionDataAggregations: { student_coc_success_events: { metric: 'student_onboarding_coc_success', aggregator: 'count' } }, + isActive: true, + }, + }); + await prisma.achievement_template.create({ + data: { + name: 'Onboarding abschließen', + metrics: ['student_onboarding_coc_success'], + templateFor: achievement_template_for_enum.Global, + group: 'student_onboarding', + groupOrder: 5, + stepName: 'Onboarding abgeschlossen', + type: achievement_type_enum.SEQUENTIAL, + subtitle: 'Jetzt durchstarten', + description: 'Dieser Text muss noch geliefert werden.', + image: 'Flugticket', + achievedImage: '', + actionName: null, + actionRedirectLink: null, + actionType: null, + condition: 'student_coc_success_events > 0', + conditionDataAggregations: { student_coc_success_events: { metric: 'student_onboarding_coc_success', aggregator: 'count' } }, + isActive: true, + }, + }); +}; From 0e4d7783708617dcaecdfed47aabd763c440bc61 Mon Sep 17 00:00:00 2001 From: Lucas Date: Thu, 18 Jan 2024 15:52:16 +0100 Subject: [PATCH 04/13] feat: tests for student and pupil conducted match appointment --- common/achievement/template.ts | 5 +- integration-tests/15_achievements.ts | 551 ++++++++++++++++++++++++++- 2 files changed, 546 insertions(+), 10 deletions(-) diff --git a/common/achievement/template.ts b/common/achievement/template.ts index cb9958b22..6c90bf683 100644 --- a/common/achievement/template.ts +++ b/common/achievement/template.ts @@ -57,7 +57,10 @@ async function getTemplatesByMetrics(metricsForAction: Metric[]) { return []; } for (const metric of metricsForAction) { - templatesForAction = [...templatesForAction, ...templatesByMetric.get(metric.metricName)]; + const templatesForMetric = templatesByMetric.get(metric.metricName); + if (templatesForMetric) { + templatesForAction = [...templatesForAction, ...templatesForMetric]; + } } return templatesForAction; } diff --git a/integration-tests/15_achievements.ts b/integration-tests/15_achievements.ts index 9dd3ea7a6..e78d6b6c4 100644 --- a/integration-tests/15_achievements.ts +++ b/integration-tests/15_achievements.ts @@ -1,20 +1,23 @@ -import { createNewStudent } from './01_user'; +import { createNewPupil, createNewStudent, pupilTwo, studentOne } from './01_user'; import { test } from './base'; +import { v4 as generateUUID } from 'uuid'; import { screenerOne } from './02_screening'; import { adminClient } from './base/clients'; import { prisma } from '../common/prisma'; -import { achievement_template_for_enum, achievement_type_enum, achievement_action_type_enum } from '@prisma/client'; -import { getUser } from '../common/user'; +import { achievement_template_for_enum, achievement_type_enum, achievement_action_type_enum, lecture_appointmenttype_enum } from '@prisma/client'; +import { User, getUser } from '../common/user'; import { getLogger } from '../common/logger/logger'; +import { Match } from '../graphql/generated'; +import { _createFixedToken } from '../common/secret/token'; const logger = getLogger('Token'); -void test('Reward onboarding achievement sequence', async () => { +void test('Reward student onboarding achievement sequence', async () => { await adminClient.request(`mutation ResetRateLimits { _resetRateLimits }`); await createStudentOnboardingTemplates(); // Verify Email - const { student } = await createNewStudent(); + const { student, client } = await createNewStudent(); const user = await getUser(student.userID); const student_onboarding_1 = await prisma.user_achievement.findFirst({ @@ -87,12 +90,161 @@ void test('Reward onboarding achievement sequence', async () => { } logger.info('The Achievement 4 adn 5 for group student_onboarding were created and found when creating a CoC'); }); -// void test('Reward final sequential achievement', async () => {}); -// void test('Reward tiered achievement', async () => {}); +void test('Reward pupil onboarding achievement sequence', async () => { + await adminClient.request(`mutation ResetRateLimits { _resetRateLimits }`); + await createPupilOnboardingTemplates(); + + // Verify Email + const { pupil } = await createNewPupil(); + const user = await getUser(pupil.userID); -// void test('Reward new record streak achievement', async () => {}); -// void test('Broken Streak', async () => {}); + const pupil_onboarding_1 = await prisma.user_achievement.findFirst({ + where: { + group: 'pupil_onboarding', + groupOrder: 1, + achievedAt: { not: null }, + userId: user.userID, + }, + }); + if (!pupil_onboarding_1) { + throw new Error(`There was no achievement created or found during or after the email verification process.`); + } + logger.info('The Achievement 1 for group pupil_onboarding was created and found when verifying a users E-Mail.'); + // Screening + await adminClient.request(` + mutation RequestScreening { pupilCreateScreening(pupilId: ${pupil.pupil.id})} + `); + const pupil_onboarding_2 = await prisma.user_achievement.findFirst({ + where: { + group: 'pupil_onboarding', + groupOrder: 3, + achievedAt: { not: null }, + userId: user.userID, + }, + }); + const pupil_onboarding_3 = await prisma.user_achievement.findFirst({ + where: { + group: 'pupil_onboarding', + groupOrder: 4, + userId: user.userID, + }, + }); + if (!pupil_onboarding_2) { + throw new Error(`There was no achievement created or found during or after the screening process`); + } else if (!pupil_onboarding_3) { + throw new Error(`There was no final achievement created or found after the screening process`); + } + logger.info('The Achievement 3 and 4 for group pupil_onboarding were created and found when screeing a pupil'); +}); + +void test('Reward student conducted match appointment', async () => { + await adminClient.request(`mutation ResetRateLimits { _resetRateLimits }`); + await createStudentConductedMatchAppointmentTemplates(); + const { student, client } = await studentOne; + const user = await getUser(student.userID); + await _createFixedToken(user, `authtokenStudent`); + await client.request(` + mutation auth { loginToken(token: "authtokenStudent") } + `); + + const uuid = generateUUID(); + const match = await prisma.match.create({ + data: { + uuid, + source: 'matchedinternal', + matchPool: 'lern-fair-now', + studentId: student.student.id, + pupilId: 1, + }, + }); + const dates = createDates(); + generateLectures(dates, match, user); + await client.request(` + mutation StudentJoinMatchMeeting { matchMeetingJoin(matchId:${match.id}) } + `); + + const student_joined_match_meeting_achievements = await prisma.user_achievement.findMany({ + where: { + group: 'student_conduct_match_appointment', + userId: user.userID, + }, + }); + if (student_joined_match_meeting_achievements.length === 0) { + throw new Error(`There was no achievement created for the tierd achievement of group student_conducted_match_appointment`); + } + logger.info( + `There were ${student_joined_match_meeting_achievements.length} achivements of the group student_conducted_match_appointment found, after joining a match meeting` + ); +}); + +void test('Reward pupil conducted match appointment', async () => { + await adminClient.request(`mutation ResetRateLimits { _resetRateLimits }`); + await createPupilConductedMatchMeetingTemplates(); + const { pupil, client } = await pupilTwo; + const user = await getUser(pupil.userID); + + const match = await prisma.match.findFirst({ + where: { + pupilId: pupil.pupil.id, + dissolved: false, + }, + select: { id: true }, + }); + await client.request(` + mutation StudentJoinMatchMeeting { matchMeetingJoin(matchId:${match.id}) } + `); + const pupil_joined_match_meeting_achievements = await prisma.user_achievement.findMany({ + where: { + group: 'pupil_conduct_match_appointment', + userId: user.userID, + }, + }); + logger.info( + `There were ${pupil_joined_match_meeting_achievements.length} achivements of the group pupil_conducted_match_appointment found, after joining a match meeting` + ); +}); + +/* -------------- additional functions for template and data creation ------------- */ +function createDates(): Date[] { + const today = new Date(); + const dates: Date[] = []; + for (let i = 0; i < 5; i++) { + dates[i] = new Date(today); + dates[i].setDate(today.getDate() + i * 7); + } + return dates; +} + +function generateLectures(dates: Date[], match: Match, user: User) { + dates.forEach(async (date) => { + await prisma.lecture.create({ + data: { + createdAt: new Date(), + updatedAt: new Date(), + start: date, + duration: 60, + subcourseId: null, + matchId: match.id, + + appointmentType: lecture_appointmenttype_enum.match, + title: null, + description: null, + isCanceled: false, + organizerIds: [user.userID], + participantIds: [], + declinedBy: [], + zoomMeetingId: null, + zoomMeetingReport: [], + instructorId: null, + override_meeting_link: null, + }, + select: { + id: true, + }, + }); + }); +} const createStudentOnboardingTemplates = async () => { await prisma.achievement_template.create({ @@ -203,3 +355,384 @@ const createStudentOnboardingTemplates = async () => { }, }); }; +const createPupilOnboardingTemplates = async () => { + await prisma.achievement_template.create({ + data: { + name: 'Onboarding abschließen', + metrics: ['pupil_onboarding_verified'], + templateFor: achievement_template_for_enum.Global, + group: 'pupil_onboarding', + groupOrder: 1, + stepName: 'Verifizieren', + type: achievement_type_enum.SEQUENTIAL, + subtitle: 'Jetzt durchstarten', + description: 'Dieser Text muss noch geliefert werden.', + image: 'Puzzle_00', + achievedImage: '', + actionName: 'E-Mail erneut senden', + actionRedirectLink: '', + actionType: achievement_action_type_enum.Action, + condition: 'pupil_verified_events > 0', + conditionDataAggregations: { pupil_verified_events: { metric: 'pupil_onboarding_verified', aggregator: 'count' } }, + isActive: true, + }, + }); + await prisma.achievement_template.create({ + data: { + name: 'Onboarding abschließen', + metrics: ['pupil_onboarding_appointment_booked'], + templateFor: achievement_template_for_enum.Global, + group: 'pupil_onboarding', + groupOrder: 2, + stepName: 'Kennenlerngespräch buchen', + type: achievement_type_enum.SEQUENTIAL, + subtitle: 'Jetzt durchstarten', + description: 'Dieser Text muss noch geliefert werden.', + image: 'Puzzle_01', + achievedImage: '', + actionName: 'Termin vereinbaren', + actionRedirectLink: 'https://calendly.com', + actionType: achievement_action_type_enum.Action, + condition: 'pupil_appointment_booked_events > 0', + conditionDataAggregations: { + pupil_appointment_booked_events: { metric: 'pupil_onboarding_appointment_booked', aggregator: 'count' }, + }, + isActive: false, + }, + }); + await prisma.achievement_template.create({ + data: { + name: 'Onboarding abschließen', + metrics: ['pupil_onboarding_screened'], + templateFor: achievement_template_for_enum.Global, + group: 'pupil_onboarding', + groupOrder: 3, + stepName: 'Screening absolvieren', + type: achievement_type_enum.SEQUENTIAL, + subtitle: 'Jetzt durchstarten', + description: 'Dieser Text muss noch geliefert werden.', + image: 'Puzzle_02', + achievedImage: '', + actionName: 'Screening absolvieren', + actionRedirectLink: '', + actionType: achievement_action_type_enum.Appointment, + condition: 'pupil_screened_events > 0', + conditionDataAggregations: { pupil_screened_events: { metric: 'pupil_onboarding_screened', aggregator: 'count' } }, + isActive: true, + }, + }); + + await prisma.achievement_template.create({ + data: { + name: 'Onboarding abschließen', + metrics: ['pupil_onboarding_screened'], + templateFor: achievement_template_for_enum.Global, + group: 'pupil_onboarding', + groupOrder: 4, + stepName: 'Onboarding abgeschlossen', + type: achievement_type_enum.SEQUENTIAL, + subtitle: 'Jetzt durchstarten', + description: 'Dieser Text muss noch geliefert werden.', + image: 'Flugticket', + achievedImage: '', + actionName: null, + actionRedirectLink: null, + actionType: null, + condition: 'pupil_screened_events > 0', + conditionDataAggregations: { pupil_screened_events: { metric: 'pupil_onboarding_screened', aggregator: 'count' } }, + isActive: true, + }, + }); +}; +const createStudentConductedMatchAppointmentTemplates = async () => { + await prisma.achievement_template.create({ + data: { + name: '1. durchgeführter Termin', + metrics: ['student_conducted_match_appointment'], + templateFor: achievement_template_for_enum.Global_Matches, + group: 'student_conduct_match_appointment', + groupOrder: 1, + stepName: '', + type: achievement_type_enum.TIERED, + subtitle: '1:1 Lernunterstützungen', + description: 'Dieser Text muss noch geliefert werden.', + image: 'Polaroid_01', + achievedImage: '', + actionName: 'Absolviere deinen ersten Termin, um diesen Erfolg zu erhalten', + actionRedirectLink: null, + actionType: achievement_action_type_enum.Action, + achievedText: 'Juhu! Dieser Text muss noch geliefert werden', + condition: 'student_match_appointments_count > 0', + conditionDataAggregations: { + student_match_appointments_count: { metric: 'student_conducted_match_appointment', aggregator: 'count', valueToAchieve: 1 }, + }, + isActive: true, + }, + }); + await prisma.achievement_template.create({ + data: { + name: '3 durchgeführte Termine', + metrics: ['student_conducted_match_appointment'], + templateFor: achievement_template_for_enum.Global_Matches, + group: 'student_conduct_match_appointment', + groupOrder: 2, + stepName: '', + type: achievement_type_enum.TIERED, + subtitle: '1:1 Lernunterstützungen', + description: 'Dieser Text muss noch geliefert werden.', + image: 'Polaroid_02', + achievedImage: '', + actionName: null, + actionRedirectLink: null, + actionType: null, + achievedText: 'Juhu! Dieser Text muss noch geliefert werden', + condition: 'student_match_appointments_count > 2', + conditionDataAggregations: { + student_match_appointments_count: { metric: 'student_conducted_match_appointment', aggregator: 'count', valueToAchieve: 3 }, + }, + isActive: true, + }, + }); + await prisma.achievement_template.create({ + data: { + name: '5 durchgeführte Termine', + metrics: ['student_conducted_match_appointment'], + templateFor: achievement_template_for_enum.Global_Matches, + group: 'student_conduct_match_appointment', + groupOrder: 3, + stepName: '', + type: achievement_type_enum.TIERED, + subtitle: '1:1 Lernunterstützungen', + description: 'Dieser Text muss noch geliefert werden.', + image: 'Polaroid_03', + achievedImage: '', + actionName: null, + actionRedirectLink: null, + actionType: null, + achievedText: 'Juhu! Dieser Text muss noch geliefert werden', + condition: 'student_match_appointments_count > 4', + conditionDataAggregations: { + student_match_appointments_count: { metric: 'student_conducted_match_appointment', aggregator: 'count', valueToAchieve: 5 }, + }, + isActive: true, + }, + }); + await prisma.achievement_template.create({ + data: { + name: '10 durchgeführte Termine', + metrics: ['student_conducted_match_appointment'], + templateFor: achievement_template_for_enum.Global_Matches, + group: 'student_conduct_match_appointment', + groupOrder: 4, + stepName: '', + type: achievement_type_enum.TIERED, + subtitle: '1:1 Lernunterstützungen', + description: 'Dieser Text muss noch geliefert werden.', + image: 'Polaroid_04', + achievedImage: '', + actionName: null, + actionRedirectLink: null, + actionType: null, + achievedText: 'Juhu! Dieser Text muss noch geliefert werden', + condition: 'student_match_appointments_count > 9', + conditionDataAggregations: { + student_match_appointments_count: { metric: 'student_conducted_match_appointment', aggregator: 'count', valueToAchieve: 10 }, + }, + isActive: true, + }, + }); + await prisma.achievement_template.create({ + data: { + name: '15 durchgeführte Termine', + metrics: ['student_conducted_match_appointment'], + templateFor: achievement_template_for_enum.Global_Matches, + group: 'student_conduct_match_appointment', + groupOrder: 5, + stepName: '', + type: achievement_type_enum.TIERED, + subtitle: '1:1 Lernunterstützungen', + description: 'Dieser Text muss noch geliefert werden.', + image: 'Polaroid_05', + achievedImage: '', + actionName: null, + actionRedirectLink: null, + actionType: null, + achievedText: 'Juhu! Dieser Text muss noch geliefert werden', + condition: 'student_match_appointments_count > 14', + conditionDataAggregations: { + student_match_appointments_count: { metric: 'student_conducted_match_appointment', aggregator: 'count', valueToAchieve: 15 }, + }, + isActive: true, + }, + }); + await prisma.achievement_template.create({ + data: { + name: '25 durchgeführte Termine', + metrics: ['student_conducted_match_appointment'], + templateFor: achievement_template_for_enum.Global_Matches, + group: 'student_conduct_match_appointment', + groupOrder: 6, + stepName: '', + type: achievement_type_enum.TIERED, + subtitle: '1:1 Lernunterstützungen', + description: 'Dieser Text muss noch geliefert werden.', + image: 'Polaroid_06', + achievedImage: '', + actionName: null, + actionRedirectLink: null, + actionType: null, + achievedText: 'Juhu! Dieser Text muss noch geliefert werden', + condition: 'student_match_appointments_count > 24', + conditionDataAggregations: { + student_match_appointments_count: { metric: 'student_conducted_match_appointment', aggregator: 'count', valueToAchieve: 25 }, + }, + isActive: true, + }, + }); +}; +const createPupilConductedMatchMeetingTemplates = async () => { + await prisma.achievement_template.create({ + data: { + name: '1. durchgeführter Termin', + metrics: ['pupil_conducted_match_appointment'], + templateFor: achievement_template_for_enum.Global_Matches, + group: 'pupil_conduct_match_appointment', + groupOrder: 1, + stepName: '', + type: achievement_type_enum.TIERED, + subtitle: '1:1 Lernunterstützungen', + description: 'Dieser Text muss noch geliefert werden.', + image: 'Polaroid_01', + achievedImage: '', + actionName: 'Absolviere deinen ersten Termin, um diesen Erfolg zu erhalten', + actionRedirectLink: null, + actionType: achievement_action_type_enum.Action, + achievedText: 'Juhu! Dieser Text muss noch geliefert werden', + condition: 'pupil_match_appointments_count > 0', + conditionDataAggregations: { + pupil_match_appointments_count: { metric: 'pupil_conducted_match_appointment', aggregator: 'count', valueToAchieve: 1 }, + }, + isActive: true, + }, + }); + await prisma.achievement_template.create({ + data: { + name: '3 durchgeführte Termine', + metrics: ['pupil_conducted_match_appointment'], + templateFor: achievement_template_for_enum.Global_Matches, + group: 'pupil_conduct_match_appointment', + groupOrder: 2, + stepName: '', + type: achievement_type_enum.TIERED, + subtitle: '1:1 Lernunterstützungen', + description: 'Dieser Text muss noch geliefert werden.', + image: 'Polaroid_02', + achievedImage: '', + actionName: null, + actionRedirectLink: null, + actionType: null, + achievedText: 'Juhu! Dieser Text muss noch geliefert werden', + condition: 'pupil_match_appointments_count > 2', + conditionDataAggregations: { + pupil_match_appointments_count: { metric: 'pupil_conducted_match_appointment', aggregator: 'count', valueToAchieve: 3 }, + }, + isActive: true, + }, + }); + await prisma.achievement_template.create({ + data: { + name: '5 durchgeführte Termine', + metrics: ['pupil_conducted_match_appointment'], + templateFor: achievement_template_for_enum.Global_Matches, + group: 'pupil_conduct_match_appointment', + groupOrder: 3, + stepName: '', + type: achievement_type_enum.TIERED, + subtitle: '1:1 Lernunterstützungen', + description: 'Dieser Text muss noch geliefert werden.', + image: 'Polaroid_03', + achievedImage: '', + actionName: null, + actionRedirectLink: null, + actionType: null, + achievedText: 'Juhu! Dieser Text muss noch geliefert werden', + condition: 'pupil_match_appointments_count > 4', + conditionDataAggregations: { + pupil_match_appointments_count: { metric: 'pupil_conducted_match_appointment', aggregator: 'count', valueToAchieve: 5 }, + }, + isActive: true, + }, + }); + await prisma.achievement_template.create({ + data: { + name: '10 durchgeführte Termine', + metrics: ['pupil_conducted_match_appointment'], + templateFor: achievement_template_for_enum.Global_Matches, + group: 'pupil_conduct_match_appointment', + groupOrder: 4, + stepName: '', + type: achievement_type_enum.TIERED, + subtitle: '1:1 Lernunterstützungen', + description: 'Dieser Text muss noch geliefert werden.', + image: 'Polaroid_04', + achievedImage: '', + actionName: null, + actionRedirectLink: null, + actionType: null, + achievedText: 'Juhu! Dieser Text muss noch geliefert werden', + condition: 'pupil_match_appointments_count > 9', + conditionDataAggregations: { + student_conducted_match_appointments: { metric: 'pupil_conducted_match_appointment', aggregator: 'count', valueToAchieve: 10 }, + }, + isActive: true, + }, + }); + await prisma.achievement_template.create({ + data: { + name: '15 durchgeführte Termine', + metrics: ['pupil_conducted_match_appointment'], + templateFor: achievement_template_for_enum.Global_Matches, + group: 'pupil_conduct_match_appointment', + groupOrder: 5, + stepName: '', + type: achievement_type_enum.TIERED, + subtitle: '1:1 Lernunterstützungen', + description: 'Dieser Text muss noch geliefert werden.', + image: 'Polaroid_05', + achievedImage: '', + actionName: null, + actionRedirectLink: null, + actionType: null, + achievedText: 'Juhu! Dieser Text muss noch geliefert werden', + condition: 'pupil_match_appointments_count > 14', + conditionDataAggregations: { + pupil_match_appointments_count: { metric: 'pupil_conducted_match_appointment', aggregator: 'count', valueToAchieve: 15 }, + }, + isActive: true, + }, + }); + await prisma.achievement_template.create({ + data: { + name: '25 durchgeführte Termine', + metrics: ['pupil_conducted_match_appointment'], + templateFor: achievement_template_for_enum.Global_Matches, + group: 'pupil_conduct_match_appointment', + groupOrder: 6, + stepName: '', + type: achievement_type_enum.TIERED, + subtitle: '1:1 Lernunterstützungen', + description: 'Dieser Text muss noch geliefert werden.', + image: 'Polaroid_06', + achievedImage: '', + actionName: null, + actionRedirectLink: null, + actionType: null, + achievedText: 'Juhu! Dieser Text muss noch geliefert werden', + condition: 'pupil_match_appointments_count > 24', + conditionDataAggregations: { + pupil_match_appointments_count: { metric: 'pupil_conducted_match_appointment', aggregator: 'count', valueToAchieve: 25 }, + }, + isActive: true, + }, + }); +}; From fdd496fc9c0d972acbe2a08a644758414ac14902 Mon Sep 17 00:00:00 2001 From: Lucas Date: Thu, 18 Jan 2024 16:52:35 +0100 Subject: [PATCH 05/13] feat: reward student regular learning achievement test --- integration-tests/15_achievements.ts | 145 +++++++++++++++++++++++++-- 1 file changed, 139 insertions(+), 6 deletions(-) diff --git a/integration-tests/15_achievements.ts b/integration-tests/15_achievements.ts index e78d6b6c4..c1e3a2431 100644 --- a/integration-tests/15_achievements.ts +++ b/integration-tests/15_achievements.ts @@ -143,10 +143,6 @@ void test('Reward student conducted match appointment', async () => { await createStudentConductedMatchAppointmentTemplates(); const { student, client } = await studentOne; const user = await getUser(student.userID); - await _createFixedToken(user, `authtokenStudent`); - await client.request(` - mutation auth { loginToken(token: "authtokenStudent") } - `); const uuid = generateUUID(); const match = await prisma.match.create({ @@ -192,7 +188,7 @@ void test('Reward pupil conducted match appointment', async () => { select: { id: true }, }); await client.request(` - mutation StudentJoinMatchMeeting { matchMeetingJoin(matchId:${match.id}) } + mutation PupilJoinMatchMeeting { matchMeetingJoin(matchId:${match.id}) } `); const pupil_joined_match_meeting_achievements = await prisma.user_achievement.findMany({ where: { @@ -205,13 +201,88 @@ void test('Reward pupil conducted match appointment', async () => { ); }); +void test('Reward student regular learning', async () => { + await adminClient.request(`mutation ResetRateLimits { _resetRateLimits }`); + await createStudentRegularLearningTemplate(); + + const { student, client } = await studentOne; + const user = await getUser(student.userID); + + const match = await prisma.match.findFirst({ + where: { + studentId: student.student.id, + dissolved: false, + }, + select: { id: true }, + }); + // request to generate the achievement with initial record value 1 + await client.request(` + mutation StudentJoinMatchMeeting { matchMeetingJoin(matchId:${match.id}) } + `); + + const date = new Date(); + date.setDate(date.getDate() - 7); + await prisma.achievement_event.create({ + data: { + userId: user.userID, + metric: 'student_match_learned_regular', + value: 1, + createdAt: date, + action: 'student_joined_match_meeting', + relation: `match/${match.id}`, + }, + }); + // request to set the achievements record value to 2 due to the past event generated + await client.request(` + mutation StudentJoinMatchMeeting { matchMeetingJoin(matchId:${match.id}) } + `); + + const student_match_regular_learning_record = await prisma.user_achievement.findFirst({ + where: { + userId: user.userID, + group: 'student_match_regular_learning', + achievedAt: { not: null }, + recordValue: 2, + }, + }); + if (!student_match_regular_learning_record) { + throw new Error('There was no achievement created of type student_match_regular_learning when the match meeting was joined'); + } + logger.info('the student has a streak achivement, currently active, claiming them to be a regular learner'); + + await prisma.achievement_event.deleteMany({ + where: { + userId: user.userID, + metric: 'student_match_learned_regular', + relation: `match/${match.id}`, + }, + }); + await client.request(` + mutation StudentJoinMatchMeeting { matchMeetingJoin(matchId:${match.id}) } + `); + + const student_match_regular_learning = await prisma.user_achievement.findFirst({ + where: { + userId: user.userID, + group: 'student_match_regular_learning', + achievedAt: null, + recordValue: 2, + }, + }); + + if (!student_match_regular_learning) { + throw new Error('There was no achievement found of type student_match_regular_learning that has not reached the current record'); + } + logger.info('the student has a streak achivement, currently inactive, claiming them to not be a regular learner'); +}); + /* -------------- additional functions for template and data creation ------------- */ function createDates(): Date[] { const today = new Date(); const dates: Date[] = []; for (let i = 0; i < 5; i++) { dates[i] = new Date(today); - dates[i].setDate(today.getDate() + i * 7); + dates[i].setDate(today.getDate() + (i - 1) * 7); } return dates; } @@ -736,3 +807,65 @@ const createPupilConductedMatchMeetingTemplates = async () => { }, }); }; +const createStudentRegularLearningTemplate = async () => { + await prisma.achievement_template.create({ + data: { + name: 'Regelmäßiges Lernen', + metrics: ['student_match_learned_regular'], + templateFor: achievement_template_for_enum.Match, + group: 'student_match_regular_learning', + groupOrder: 1, + stepName: '', + type: achievement_type_enum.STREAK, + subtitle: 'Nachhilfe mit {{matchpartner}}', + description: 'Dieser Text muss noch geliefert werden.', + image: 'Hat_grey', + achievedImage: 'Hat_gold', + actionName: null, + actionRedirectLink: null, + actionType: null, + achievedText: 'Juhu! Rekord gebrochen.', + condition: 'student_match_learning_events > recordValue', + conditionDataAggregations: { + student_match_learning_events: { + metric: 'student_match_learned_regular', + aggregator: 'lastStreakLength', + createBuckets: 'by_weeks', + bucketAggregator: 'presenceOfEvents', + }, + }, + isActive: true, + }, + }); +}; +const createPupilRegularLearningTemplate = async () => { + await prisma.achievement_template.create({ + data: { + name: 'Regelmäßiges Lernen', + metrics: ['pupil_match_learned_regular'], + templateFor: achievement_template_for_enum.Match, + group: 'pupil_match_regular_learning', + groupOrder: 1, + stepName: '', + type: achievement_type_enum.STREAK, + subtitle: 'Nachhilfe mit {{matchpartner}}', + description: 'Dieser Text muss noch geliefert werden.', + image: 'Hat_grey', + achievedImage: 'Hat_gold', + actionName: null, + actionRedirectLink: null, + actionType: null, + achievedText: 'Juhu! Rekord gebrochen.', + condition: 'pupil_match_learning_events > recordValue', + conditionDataAggregations: { + pupil_match_learning_events: { + metric: 'pupil_match_learned_regular', + aggregator: 'lastStreakLength', + createBuckets: 'by_weeks', + bucketAggregator: 'presenceOfEvents', + }, + }, + isActive: true, + }, + }); +}; From 09234f7b600273460b35034663611c44064843a9 Mon Sep 17 00:00:00 2001 From: Lucas Date: Thu, 18 Jan 2024 16:57:03 +0100 Subject: [PATCH 06/13] feat: reward pupil regular learning achievement test --- integration-tests/15_achievements.ts | 75 ++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/integration-tests/15_achievements.ts b/integration-tests/15_achievements.ts index c1e3a2431..34dafb7d3 100644 --- a/integration-tests/15_achievements.ts +++ b/integration-tests/15_achievements.ts @@ -276,6 +276,81 @@ void test('Reward student regular learning', async () => { logger.info('the student has a streak achivement, currently inactive, claiming them to not be a regular learner'); }); +void test('Reward pupil regular learning', async () => { + await adminClient.request(`mutation ResetRateLimits { _resetRateLimits }`); + await createPupilRegularLearningTemplate(); + + const { pupil, client } = await pupilTwo; + const user = await getUser(pupil.userID); + + const match = await prisma.match.findFirst({ + where: { + pupilId: pupil.pupil.id, + dissolved: false, + }, + select: { id: true }, + }); + // request to generate the achievement with initial record value 1 + await client.request(` + mutation PupilJoinMatchMeeting { matchMeetingJoin(matchId:${match.id}) } + `); + + const date = new Date(); + date.setDate(date.getDate() - 7); + await prisma.achievement_event.create({ + data: { + userId: user.userID, + metric: 'pupil_match_learned_regular', + value: 1, + createdAt: date, + action: 'pupil_joined_match_meeting', + relation: `match/${match.id}`, + }, + }); + // request to set the achievements record value to 2 due to the past event generated + await client.request(` + mutation PupilJoinMatchMeeting { matchMeetingJoin(matchId:${match.id}) } + `); + + const pupil_match_regular_learning_record = await prisma.user_achievement.findFirst({ + where: { + userId: user.userID, + group: 'pupil_match_regular_learning', + achievedAt: { not: null }, + recordValue: 2, + }, + }); + if (!pupil_match_regular_learning_record) { + throw new Error('There was no achievement created of type pupil_match_regular_learning when the match meeting was joined'); + } + logger.info('the pupil has a streak achivement, currently active, claiming them to be a regular learner'); + + await prisma.achievement_event.deleteMany({ + where: { + userId: user.userID, + metric: 'pupil_match_regular_learning', + relation: `match/${match.id}`, + }, + }); + await client.request(` + mutation PupilJoinMatchMeeting { matchMeetingJoin(matchId:${match.id}) } + `); + + const pupil_match_regular_learning = await prisma.user_achievement.findFirst({ + where: { + userId: user.userID, + group: 'pupil_match_regular_learning', + achievedAt: null, + recordValue: 2, + }, + }); + + if (!pupil_match_regular_learning) { + throw new Error('There was no achievement found of type pupil_match_regular_learning that has not reached the current record'); + } + logger.info('the student has a streak achivement, currently inactive, claiming them to not be a regular learner'); +}); + /* -------------- additional functions for template and data creation ------------- */ function createDates(): Date[] { const today = new Date(); From ec34edb3807890abdcee6600529dc88adc7bff0b Mon Sep 17 00:00:00 2001 From: Lucas Date: Fri, 19 Jan 2024 09:29:16 +0100 Subject: [PATCH 07/13] feat: resolver tests --- integration-tests/15_achievements.ts | 201 ++++++++++++++++++++------- 1 file changed, 149 insertions(+), 52 deletions(-) diff --git a/integration-tests/15_achievements.ts b/integration-tests/15_achievements.ts index 34dafb7d3..f76fb6846 100644 --- a/integration-tests/15_achievements.ts +++ b/integration-tests/15_achievements.ts @@ -9,8 +9,7 @@ import { User, getUser } from '../common/user'; import { getLogger } from '../common/logger/logger'; import { Match } from '../graphql/generated'; import { _createFixedToken } from '../common/secret/token'; - -const logger = getLogger('Token'); +import assert from 'assert'; void test('Reward student onboarding achievement sequence', async () => { await adminClient.request(`mutation ResetRateLimits { _resetRateLimits }`); @@ -28,10 +27,7 @@ void test('Reward student onboarding achievement sequence', async () => { userId: user.userID, }, }); - if (!student_onboarding_1) { - throw new Error(`There was no achievement created or found during or after the email verification process.`); - } - logger.info('The Achievement 1 for group student_onboarding was created and found when verifying a users E-Mail.'); + assert.ok(student_onboarding_1); // Screening const { client: screenerClient } = await screenerOne; @@ -51,10 +47,7 @@ void test('Reward student onboarding achievement sequence', async () => { userId: user.userID, }, }); - if (!student_onboarding_2) { - throw new Error(`There was no achievement created or found during or after the screening process`); - } - logger.info('The Achievement 3 for group student_onboarding was created and found when screeing a tutor'); + assert.ok(student_onboarding_2); // Create Certificate of Conduct const newDate = JSON.stringify(new Date()); @@ -83,12 +76,8 @@ void test('Reward student onboarding achievement sequence', async () => { userId: user.userID, }, }); - if (!student_onboarding_3) { - throw new Error(`There was no achievement created or found during or after the creation of a CoC`); - } else if (!student_onboarding_4) { - throw new Error(`There was no final achievement created or found after the creation of a CoC`); - } - logger.info('The Achievement 4 adn 5 for group student_onboarding were created and found when creating a CoC'); + assert.ok(student_onboarding_3); + assert.ok(student_onboarding_4); }); void test('Reward pupil onboarding achievement sequence', async () => { @@ -107,10 +96,7 @@ void test('Reward pupil onboarding achievement sequence', async () => { userId: user.userID, }, }); - if (!pupil_onboarding_1) { - throw new Error(`There was no achievement created or found during or after the email verification process.`); - } - logger.info('The Achievement 1 for group pupil_onboarding was created and found when verifying a users E-Mail.'); + assert.ok(pupil_onboarding_1); // Screening await adminClient.request(` mutation RequestScreening { pupilCreateScreening(pupilId: ${pupil.pupil.id})} @@ -130,12 +116,8 @@ void test('Reward pupil onboarding achievement sequence', async () => { userId: user.userID, }, }); - if (!pupil_onboarding_2) { - throw new Error(`There was no achievement created or found during or after the screening process`); - } else if (!pupil_onboarding_3) { - throw new Error(`There was no final achievement created or found after the screening process`); - } - logger.info('The Achievement 3 and 4 for group pupil_onboarding were created and found when screeing a pupil'); + assert.ok(pupil_onboarding_2); + assert.ok(pupil_onboarding_3); }); void test('Reward student conducted match appointment', async () => { @@ -166,12 +148,8 @@ void test('Reward student conducted match appointment', async () => { userId: user.userID, }, }); - if (student_joined_match_meeting_achievements.length === 0) { - throw new Error(`There was no achievement created for the tierd achievement of group student_conducted_match_appointment`); - } - logger.info( - `There were ${student_joined_match_meeting_achievements.length} achivements of the group student_conducted_match_appointment found, after joining a match meeting` - ); + assert.ok(student_joined_match_meeting_achievements[0]); + assert.notStrictEqual(student_joined_match_meeting_achievements.length, 0); }); void test('Reward pupil conducted match appointment', async () => { @@ -196,9 +174,8 @@ void test('Reward pupil conducted match appointment', async () => { userId: user.userID, }, }); - logger.info( - `There were ${pupil_joined_match_meeting_achievements.length} achivements of the group pupil_conducted_match_appointment found, after joining a match meeting` - ); + assert.ok(pupil_joined_match_meeting_achievements); + assert.notStrictEqual(pupil_joined_match_meeting_achievements.length, 0); }); void test('Reward student regular learning', async () => { @@ -245,10 +222,7 @@ void test('Reward student regular learning', async () => { recordValue: 2, }, }); - if (!student_match_regular_learning_record) { - throw new Error('There was no achievement created of type student_match_regular_learning when the match meeting was joined'); - } - logger.info('the student has a streak achivement, currently active, claiming them to be a regular learner'); + assert.ok(student_match_regular_learning_record); await prisma.achievement_event.deleteMany({ where: { @@ -269,11 +243,7 @@ void test('Reward student regular learning', async () => { recordValue: 2, }, }); - - if (!student_match_regular_learning) { - throw new Error('There was no achievement found of type student_match_regular_learning that has not reached the current record'); - } - logger.info('the student has a streak achivement, currently inactive, claiming them to not be a regular learner'); + assert.ok(student_match_regular_learning); }); void test('Reward pupil regular learning', async () => { @@ -320,10 +290,7 @@ void test('Reward pupil regular learning', async () => { recordValue: 2, }, }); - if (!pupil_match_regular_learning_record) { - throw new Error('There was no achievement created of type pupil_match_regular_learning when the match meeting was joined'); - } - logger.info('the pupil has a streak achivement, currently active, claiming them to be a regular learner'); + assert.ok(pupil_match_regular_learning_record); await prisma.achievement_event.deleteMany({ where: { @@ -344,11 +311,141 @@ void test('Reward pupil regular learning', async () => { recordValue: 2, }, }); + assert.ok(pupil_match_regular_learning); +}); - if (!pupil_match_regular_learning) { - throw new Error('There was no achievement found of type pupil_match_regular_learning that has not reached the current record'); - } - logger.info('the student has a streak achivement, currently inactive, claiming them to not be a regular learner'); +void test('Resolver my achievements', async () => { + await adminClient.request(`mutation ResetRateLimits { _resetRateLimits }`); + const { client: studentClient } = await studentOne; + const { client: pupilClient } = await pupilTwo; + + const { me: studentMe } = await studentClient.request(` + query achievements { + me { + achievements { + id + } + } + } + `); + const { achievements: studentAchievements } = studentMe; + assert.ok(studentAchievements); + assert.notStrictEqual(studentAchievements.length, 0); + + const { me: pupilMe } = await pupilClient.request(` + query achievements { + me { + achievements { + id + } + } + } + `); + const { achievements: pupilAchievements } = pupilMe; + assert.ok(pupilAchievements); + assert.notStrictEqual(pupilAchievements.length, 0); +}); + +void test('Resolver further (INACTIVE) achievements', async () => { + await adminClient.request(`mutation ResetRateLimits { _resetRateLimits }`); + const { client: studentClient } = await studentOne; + const { client: pupilClient } = await pupilTwo; + + const { me: studentMe } = await studentClient.request(` + query furtherAchievements { + me { + furtherAchievements { + id + } + } + } + `); + const { furtherAchievements: furtherStudentAchievements } = studentMe; + assert.ok(furtherStudentAchievements); + assert.notStrictEqual(furtherStudentAchievements.length, 0); + + const { me: pupilMe } = await pupilClient.request(` + query furtherAchievements { + me { + furtherAchievements { + id + } + } + } + `); + const { furtherAchievements: furtherPupilAchievements } = pupilMe; + assert.ok(furtherPupilAchievements); + assert.notStrictEqual(furtherPupilAchievements.length, 0); +}); + +void test('Resolver next step achievements', async () => { + await adminClient.request(`mutation ResetRateLimits { _resetRateLimits }`); + const { client: studentClient } = await studentOne; + const { client: pupilClient } = await pupilTwo; + + const { me: studentMe } = await studentClient.request(` + query nextStepAchievements { + me { + nextStepAchievements { + id + } + } + } + `); + const { nextStepAchievements: nextStepStudentAchievements } = studentMe; + assert.ok(nextStepStudentAchievements); + + const { me: pupilMe } = await pupilClient.request(` + query nextStepAchievements { + me { + nextStepAchievements { + id + } + } + } + `); + const { nextStepAchievements: nextStepPupilAchievements } = pupilMe; + assert.ok(nextStepPupilAchievements); +}); + +void test('Resolver achievement by id', async () => { + await adminClient.request(`mutation ResetRateLimits { _resetRateLimits }`); + const { client: studentClient, student } = await studentOne; + const { client: pupilClient, pupil } = await pupilTwo; + + const { id: studentAchievementId } = await prisma.user_achievement.findFirst({ + where: { userId: student.userID }, + select: { id: true }, + }); + const { me: studentMe } = await studentClient.request(` + query achievementById { + me { + achievement(id:${studentAchievementId}) { + id + } + } + } + `); + const { achievement: studentAchievement } = studentMe; + assert.ok(studentAchievement); + assert.strictEqual(studentAchievement.id, studentAchievementId); + + const { id: pupilAchievementId } = await prisma.user_achievement.findFirst({ + where: { userId: pupil.userID }, + select: { id: true }, + }); + const { me: pupilMe } = await pupilClient.request(` + query achievementById { + me { + achievement(id:${pupilAchievementId}) { + id + } + } + } + `); + const { achievement: pupilAchievement } = pupilMe; + assert.ok(pupilAchievement); + assert.strictEqual(pupilAchievement.id, pupilAchievementId); }); /* -------------- additional functions for template and data creation ------------- */ From 16010ef7e99f4d39dacc2c3b158e2a749830772d Mon Sep 17 00:00:00 2001 From: Lucas Date: Fri, 19 Jan 2024 10:16:57 +0100 Subject: [PATCH 08/13] fix: resolve wrong variable name --- common/achievement/util.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/common/achievement/util.ts b/common/achievement/util.ts index 36b79f8e8..69c281afb 100644 --- a/common/achievement/util.ts +++ b/common/achievement/util.ts @@ -51,7 +51,7 @@ export async function getBucketContext(userID: string, relation?: string): Promi let matches = []; if (!relationType || relationType === 'match') { - whereClause[`${userType}Id`] = userId; + whereClause[`${userType}Id`] = id; matches = await prisma.match.findMany({ where: { ...whereClause, [`${userType}Id`]: id }, select: { @@ -64,7 +64,10 @@ export async function getBucketContext(userID: string, relation?: string): Promi let subcourses = []; if (!relationType || relationType === 'subcourse') { delete whereClause[`${userType}Id`]; - const userClause = userType === 'student' ? { subcourse_instructors_student: { some: { studentId: userId } } } : { subcourse_participants_pupil: { some: { pupilId: userId } } }; + const userClause = + userType === 'student' + ? { subcourse_instructors_student: { some: { studentId: id } } } + : { subcourse_participants_pupil: { some: { pupilId: id } } }; const subcourseWhere = { ...whereClause, ...userClause }; subcourses = await prisma.subcourse.findMany({ where: subcourseWhere, From 155eebb065de671402a32d839cda06633babc6d8 Mon Sep 17 00:00:00 2001 From: Daniel Henkel Date: Sat, 20 Jan 2024 12:15:07 +0100 Subject: [PATCH 09/13] enable gamification in integration tests --- .env.integration-tests | 4 +++- .env.integration-tests.debug | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.env.integration-tests b/.env.integration-tests index 0d92d71bf..d676ba990 100644 --- a/.env.integration-tests +++ b/.env.integration-tests @@ -17,4 +17,6 @@ ZOOM_MEETING_SDK_CLIENT_SECRET="ZOOM_MEETING_SDK_CLIENT_SECRET" ZOOM_MEETING_SDK_CLIENT_ID="ZOOM_MEETING_SDK_CLIENT_ID" ZOOM_API_KEY="ZOOM_API_KEY" ZOOM_API_SECRET="ZOOM_API_SECRET" -ZOOM_ACCOUNT_ID="ZOOM_ACCOUNT_ID" \ No newline at end of file +ZOOM_ACCOUNT_ID="ZOOM_ACCOUNT_ID" + +GAMIFICATION_ACTIVE=true diff --git a/.env.integration-tests.debug b/.env.integration-tests.debug index 881b95069..9e3f11514 100644 --- a/.env.integration-tests.debug +++ b/.env.integration-tests.debug @@ -17,4 +17,6 @@ ZOOM_MEETING_SDK_CLIENT_SECRET="ZOOM_MEETING_SDK_CLIENT_SECRET" ZOOM_MEETING_SDK_CLIENT_ID="ZOOM_MEETING_SDK_CLIENT_ID" ZOOM_API_KEY="ZOOM_API_KEY" ZOOM_API_SECRET="ZOOM_API_SECRET" -ZOOM_ACCOUNT_ID="ZOOM_ACCOUNT_ID" \ No newline at end of file +ZOOM_ACCOUNT_ID="ZOOM_ACCOUNT_ID" + +GAMIFICATION_ACTIVE=true From 68fde20a83caf6fb81280e4ff7094de620fe7fb4 Mon Sep 17 00:00:00 2001 From: Daniel Henkel Date: Sat, 20 Jan 2024 12:24:05 +0100 Subject: [PATCH 10/13] run workflows on all PRs --- .github/workflows/node.js.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 88779bab0..d2a44df9c 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -2,7 +2,7 @@ name: Master Barrier on: pull_request: - branches: [master] + types: ['opened', 'edited', 'reopened', 'synchronize'] jobs: integration: From 6f1b233ce06d83acac6d413bfe3cfd325e9b6e95 Mon Sep 17 00:00:00 2001 From: Lucas Date: Mon, 22 Jan 2024 14:20:54 +0100 Subject: [PATCH 11/13] fix: case changes, purge function for templates, use queries --- common/achievement/template.ts | 6 +- common/achievement/util.ts | 2 - integration-tests/15_achievements.ts | 216 +++++++++++++++++---------- 3 files changed, 145 insertions(+), 79 deletions(-) diff --git a/common/achievement/template.ts b/common/achievement/template.ts index 6c90bf683..7b21948ae 100644 --- a/common/achievement/template.ts +++ b/common/achievement/template.ts @@ -15,8 +15,12 @@ export enum TemplateSelectEnum { // string == metricId, group const achievementTemplates: Map> = new Map(); +export function purgeAchievementTemplates() { + achievementTemplates.clear(); +} + async function getAchievementTemplates(select: TemplateSelectEnum): Promise> { - if (!achievementTemplates.has(select) || !achievementTemplates[select]) { + if (!achievementTemplates.has(select)) { achievementTemplates.set(select, new Map()); const templatesFromDB = await prisma.achievement_template.findMany({ diff --git a/common/achievement/util.ts b/common/achievement/util.ts index 69c281afb..7ff4db3cf 100644 --- a/common/achievement/util.ts +++ b/common/achievement/util.ts @@ -51,7 +51,6 @@ export async function getBucketContext(userID: string, relation?: string): Promi let matches = []; if (!relationType || relationType === 'match') { - whereClause[`${userType}Id`] = id; matches = await prisma.match.findMany({ where: { ...whereClause, [`${userType}Id`]: id }, select: { @@ -63,7 +62,6 @@ export async function getBucketContext(userID: string, relation?: string): Promi let subcourses = []; if (!relationType || relationType === 'subcourse') { - delete whereClause[`${userType}Id`]; const userClause = userType === 'student' ? { subcourse_instructors_student: { some: { studentId: id } } } diff --git a/integration-tests/15_achievements.ts b/integration-tests/15_achievements.ts index f76fb6846..c53f7a459 100644 --- a/integration-tests/15_achievements.ts +++ b/integration-tests/15_achievements.ts @@ -1,25 +1,34 @@ import { createNewPupil, createNewStudent, pupilTwo, studentOne } from './01_user'; import { test } from './base'; -import { v4 as generateUUID } from 'uuid'; import { screenerOne } from './02_screening'; import { adminClient } from './base/clients'; import { prisma } from '../common/prisma'; import { achievement_template_for_enum, achievement_type_enum, achievement_action_type_enum, lecture_appointmenttype_enum } from '@prisma/client'; import { User, getUser } from '../common/user'; -import { getLogger } from '../common/logger/logger'; import { Match } from '../graphql/generated'; import { _createFixedToken } from '../common/secret/token'; import assert from 'assert'; +import { purgeAchievementTemplates } from '../common/achievement/template'; + +async function createTemplates() { + purgeAchievementTemplates(); + await createStudentOnboardingTemplates(); + await createPupilOnboardingTemplates(); + await createStudentConductedMatchAppointmentTemplates(); + await createPupilConductedMatchMeetingTemplates(); + await createStudentRegularLearningTemplate(); + await createPupilRegularLearningTemplate(); +} void test('Reward student onboarding achievement sequence', async () => { await adminClient.request(`mutation ResetRateLimits { _resetRateLimits }`); - await createStudentOnboardingTemplates(); + await createTemplates(); // Verify Email - const { student, client } = await createNewStudent(); + const { student } = await createNewStudent(); const user = await getUser(student.userID); - const student_onboarding_1 = await prisma.user_achievement.findFirst({ + const studentOnboarding1 = await prisma.user_achievement.findFirst({ where: { group: 'student_onboarding', groupOrder: 1, @@ -27,7 +36,7 @@ void test('Reward student onboarding achievement sequence', async () => { userId: user.userID, }, }); - assert.ok(student_onboarding_1); + assert.ok(studentOnboarding1); // Screening const { client: screenerClient } = await screenerOne; @@ -39,7 +48,7 @@ void test('Reward student onboarding achievement sequence', async () => { ) } `); - const student_onboarding_2 = await prisma.user_achievement.findFirst({ + const studentOnboarding2 = await prisma.user_achievement.findFirst({ where: { group: 'student_onboarding', groupOrder: 3, @@ -47,7 +56,7 @@ void test('Reward student onboarding achievement sequence', async () => { userId: user.userID, }, }); - assert.ok(student_onboarding_2); + assert.ok(studentOnboarding2); // Create Certificate of Conduct const newDate = JSON.stringify(new Date()); @@ -61,7 +70,7 @@ void test('Reward student onboarding achievement sequence', async () => { ) } `); - const student_onboarding_3 = await prisma.user_achievement.findFirst({ + const studentOnboarding3 = await prisma.user_achievement.findFirst({ where: { group: 'student_onboarding', groupOrder: 4, @@ -69,26 +78,25 @@ void test('Reward student onboarding achievement sequence', async () => { userId: user.userID, }, }); - const student_onboarding_4 = await prisma.user_achievement.findFirst({ + const studentOnboarding4 = await prisma.user_achievement.findFirst({ where: { group: 'student_onboarding', groupOrder: 5, userId: user.userID, }, }); - assert.ok(student_onboarding_3); - assert.ok(student_onboarding_4); + assert.ok(studentOnboarding3); + assert.ok(studentOnboarding4); }); void test('Reward pupil onboarding achievement sequence', async () => { await adminClient.request(`mutation ResetRateLimits { _resetRateLimits }`); - await createPupilOnboardingTemplates(); // Verify Email const { pupil } = await createNewPupil(); const user = await getUser(pupil.userID); - const pupil_onboarding_1 = await prisma.user_achievement.findFirst({ + const pupilOnboarding1 = await prisma.user_achievement.findFirst({ where: { group: 'pupil_onboarding', groupOrder: 1, @@ -96,12 +104,12 @@ void test('Reward pupil onboarding achievement sequence', async () => { userId: user.userID, }, }); - assert.ok(pupil_onboarding_1); + assert.ok(pupilOnboarding1); // Screening await adminClient.request(` mutation RequestScreening { pupilCreateScreening(pupilId: ${pupil.pupil.id})} `); - const pupil_onboarding_2 = await prisma.user_achievement.findFirst({ + const pupilOnboarding2 = await prisma.user_achievement.findFirst({ where: { group: 'pupil_onboarding', groupOrder: 3, @@ -109,100 +117,149 @@ void test('Reward pupil onboarding achievement sequence', async () => { userId: user.userID, }, }); - const pupil_onboarding_3 = await prisma.user_achievement.findFirst({ + const pupilOnboarding3 = await prisma.user_achievement.findFirst({ where: { group: 'pupil_onboarding', groupOrder: 4, userId: user.userID, }, }); - assert.ok(pupil_onboarding_2); - assert.ok(pupil_onboarding_3); + assert.ok(pupilOnboarding2); + assert.ok(pupilOnboarding3); }); void test('Reward student conducted match appointment', async () => { await adminClient.request(`mutation ResetRateLimits { _resetRateLimits }`); - await createStudentConductedMatchAppointmentTemplates(); const { student, client } = await studentOne; const user = await getUser(student.userID); - const uuid = generateUUID(); - const match = await prisma.match.create({ - data: { - uuid, - source: 'matchedinternal', - matchPool: 'lern-fair-now', - studentId: student.student.id, - pupilId: 1, + await client.request(` + mutation { + studentCreateMatchRequest + } + `); + + const { + me: { + student: { matches }, }, - }); + } = await client.request(` + query StudentWithMatch { + me { + student { + matches { + id + uuid + dissolved + pupil { firstname lastname } + } + } + } + } + `); + const [match] = matches; + const dates = createDates(); generateLectures(dates, match, user); await client.request(` mutation StudentJoinMatchMeeting { matchMeetingJoin(matchId:${match.id}) } `); - const student_joined_match_meeting_achievements = await prisma.user_achievement.findMany({ + const studentJoinedMatchMeetingAchievements = await prisma.user_achievement.findMany({ where: { group: 'student_conduct_match_appointment', userId: user.userID, }, }); - assert.ok(student_joined_match_meeting_achievements[0]); - assert.notStrictEqual(student_joined_match_meeting_achievements.length, 0); + assert.ok(studentJoinedMatchMeetingAchievements[0]); + assert.notStrictEqual(studentJoinedMatchMeetingAchievements.length, 0); }); void test('Reward pupil conducted match appointment', async () => { await adminClient.request(`mutation ResetRateLimits { _resetRateLimits }`); - await createPupilConductedMatchMeetingTemplates(); + const { student } = await studentOne; const { pupil, client } = await pupilTwo; const user = await getUser(pupil.userID); - const match = await prisma.match.findFirst({ - where: { - pupilId: pupil.pupil.id, - dissolved: false, + await client.request(` + mutation { + pupilCreateMatchRequest + } + `); + await adminClient.request(` + mutation CreateManualMatch { + matchAdd(poolName: "lern-fair-now", studentId: ${student.student.id} pupilId: ${pupil.pupil.id}) + } + `); + const { + me: { + pupil: { matches }, }, - select: { id: true }, - }); + } = await client.request(` + query PupilWithMatch { + me { + pupil { + matches { + id + } + } + } + } + `); + const [match] = matches; await client.request(` mutation PupilJoinMatchMeeting { matchMeetingJoin(matchId:${match.id}) } `); - const pupil_joined_match_meeting_achievements = await prisma.user_achievement.findMany({ + const pupilJoinedMatchMeetingAchievements = await prisma.user_achievement.findMany({ where: { group: 'pupil_conduct_match_appointment', userId: user.userID, }, }); - assert.ok(pupil_joined_match_meeting_achievements); - assert.notStrictEqual(pupil_joined_match_meeting_achievements.length, 0); + assert.ok(pupilJoinedMatchMeetingAchievements); + assert.notStrictEqual(pupilJoinedMatchMeetingAchievements.length, 0); }); void test('Reward student regular learning', async () => { await adminClient.request(`mutation ResetRateLimits { _resetRateLimits }`); - await createStudentRegularLearningTemplate(); const { student, client } = await studentOne; const user = await getUser(student.userID); + const metric = 'student_match_learned_regular'; - const match = await prisma.match.findFirst({ - where: { - studentId: student.student.id, - dissolved: false, - }, - select: { id: true }, - }); + const { + me: { student: s1 }, + } = await client.request(` + query StudentWithMatch { + me { + student { + matches { + id + } + } + } + } + `); + const [match] = s1.matches.filter((el) => !el.dissolved); // request to generate the achievement with initial record value 1 await client.request(` mutation StudentJoinMatchMeeting { matchMeetingJoin(matchId:${match.id}) } `); + const achievement = await prisma.user_achievement.findFirst({ + where: { + userId: user.userID, + template: { metrics: { has: metric } }, + context: { path: ['relation'], equals: `match/${match.id}` }, + }, + }); + assert.strictEqual(achievement.recordValue, 1); const date = new Date(); date.setDate(date.getDate() - 7); await prisma.achievement_event.create({ data: { userId: user.userID, - metric: 'student_match_learned_regular', + metric: metric, value: 1, createdAt: date, action: 'student_joined_match_meeting', @@ -214,7 +271,7 @@ void test('Reward student regular learning', async () => { mutation StudentJoinMatchMeeting { matchMeetingJoin(matchId:${match.id}) } `); - const student_match_regular_learning_record = await prisma.user_achievement.findFirst({ + const studentMatchRegularLearningRecord = await prisma.user_achievement.findFirst({ where: { userId: user.userID, group: 'student_match_regular_learning', @@ -222,12 +279,12 @@ void test('Reward student regular learning', async () => { recordValue: 2, }, }); - assert.ok(student_match_regular_learning_record); + assert.ok(studentMatchRegularLearningRecord); await prisma.achievement_event.deleteMany({ where: { userId: user.userID, - metric: 'student_match_learned_regular', + metric: metric, relation: `match/${match.id}`, }, }); @@ -235,7 +292,7 @@ void test('Reward student regular learning', async () => { mutation StudentJoinMatchMeeting { matchMeetingJoin(matchId:${match.id}) } `); - const student_match_regular_learning = await prisma.user_achievement.findFirst({ + const studentMatchRegularLearning = await prisma.user_achievement.findFirst({ where: { userId: user.userID, group: 'student_match_regular_learning', @@ -243,34 +300,51 @@ void test('Reward student regular learning', async () => { recordValue: 2, }, }); - assert.ok(student_match_regular_learning); + assert.ok(studentMatchRegularLearning); }); void test('Reward pupil regular learning', async () => { await adminClient.request(`mutation ResetRateLimits { _resetRateLimits }`); - await createPupilRegularLearningTemplate(); const { pupil, client } = await pupilTwo; const user = await getUser(pupil.userID); + const metric = 'pupil_match_learned_regular'; - const match = await prisma.match.findFirst({ - where: { - pupilId: pupil.pupil.id, - dissolved: false, + const { + me: { + pupil: { matches }, }, - select: { id: true }, - }); + } = await client.request(` + query PupilWithMatch { + me { + pupil { + matches { + id + } + } + } + } + `); + const [match] = matches; // request to generate the achievement with initial record value 1 await client.request(` mutation PupilJoinMatchMeeting { matchMeetingJoin(matchId:${match.id}) } `); + const achievement = await prisma.user_achievement.findFirst({ + where: { + userId: user.userID, + template: { metrics: { has: metric } }, + context: { path: ['relation'], equals: `match/${match.id}` }, + }, + }); + assert.strictEqual(achievement.recordValue, 1); const date = new Date(); date.setDate(date.getDate() - 7); await prisma.achievement_event.create({ data: { userId: user.userID, - metric: 'pupil_match_learned_regular', + metric: metric, value: 1, createdAt: date, action: 'pupil_joined_match_meeting', @@ -282,16 +356,6 @@ void test('Reward pupil regular learning', async () => { mutation PupilJoinMatchMeeting { matchMeetingJoin(matchId:${match.id}) } `); - const pupil_match_regular_learning_record = await prisma.user_achievement.findFirst({ - where: { - userId: user.userID, - group: 'pupil_match_regular_learning', - achievedAt: { not: null }, - recordValue: 2, - }, - }); - assert.ok(pupil_match_regular_learning_record); - await prisma.achievement_event.deleteMany({ where: { userId: user.userID, @@ -303,7 +367,7 @@ void test('Reward pupil regular learning', async () => { mutation PupilJoinMatchMeeting { matchMeetingJoin(matchId:${match.id}) } `); - const pupil_match_regular_learning = await prisma.user_achievement.findFirst({ + const pupilMatchRegularLearning = await prisma.user_achievement.findFirst({ where: { userId: user.userID, group: 'pupil_match_regular_learning', @@ -311,7 +375,7 @@ void test('Reward pupil regular learning', async () => { recordValue: 2, }, }); - assert.ok(pupil_match_regular_learning); + assert.ok(pupilMatchRegularLearning); }); void test('Resolver my achievements', async () => { From 5750980a47593953612f0b82a90db4fc30a72689 Mon Sep 17 00:00:00 2001 From: LucasFalkowsky <114646768+LucasFalkowsky@users.noreply.github.com> Date: Thu, 25 Jan 2024 13:14:48 +0100 Subject: [PATCH 12/13] Update common/achievement/template.ts Co-authored-by: Daniel Henkel <9447057+dhenkel92@users.noreply.github.com> --- common/achievement/template.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/achievement/template.ts b/common/achievement/template.ts index 7b21948ae..39d6eddc7 100644 --- a/common/achievement/template.ts +++ b/common/achievement/template.ts @@ -15,7 +15,7 @@ export enum TemplateSelectEnum { // string == metricId, group const achievementTemplates: Map> = new Map(); -export function purgeAchievementTemplates() { +export function purgeAchievementTemplateCache() { achievementTemplates.clear(); } From d3d14a9aede3e470f1a06241c50997fe76ae7c16 Mon Sep 17 00:00:00 2001 From: Lucas Date: Thu, 25 Jan 2024 14:16:30 +0100 Subject: [PATCH 13/13] fix: fetch user achievements by group --- common/achievement/create.ts | 16 +++++++++------- integration-tests/15_achievements.ts | 4 ++-- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/common/achievement/create.ts b/common/achievement/create.ts index a84116cdf..866602d2d 100644 --- a/common/achievement/create.ts +++ b/common/achievement/create.ts @@ -55,19 +55,21 @@ async function _createAchievement(currentTemplate: achievem const templatesByGroup = await getAchievementTemplates(TemplateSelectEnum.BY_GROUP); const templatesForGroup = templatesByGroup.get(currentTemplate.group).sort((a, b) => a.groupOrder - b.groupOrder); - const contextHasRelation = context && Object.keys(context).includes('relation'); + const contextHasRelation = Object.keys(context).includes('relation'); const userAchievementsByGroup = await prisma.user_achievement.findMany({ where: { template: { group: currentTemplate.group, }, userId, - AND: contextHasRelation && { - context: { - path: ['relation'], - equals: context['relation'], - }, - }, + ...(contextHasRelation + ? { + context: { + path: ['relation'], + equals: context['relation'], + }, + } + : {}), }, orderBy: { template: { groupOrder: 'asc' } }, }); diff --git a/integration-tests/15_achievements.ts b/integration-tests/15_achievements.ts index c53f7a459..e8a2e31af 100644 --- a/integration-tests/15_achievements.ts +++ b/integration-tests/15_achievements.ts @@ -8,10 +8,10 @@ import { User, getUser } from '../common/user'; import { Match } from '../graphql/generated'; import { _createFixedToken } from '../common/secret/token'; import assert from 'assert'; -import { purgeAchievementTemplates } from '../common/achievement/template'; +import { purgeAchievementTemplateCache } from '../common/achievement/template'; async function createTemplates() { - purgeAchievementTemplates(); + purgeAchievementTemplateCache(); await createStudentOnboardingTemplates(); await createPupilOnboardingTemplates(); await createStudentConductedMatchAppointmentTemplates();