226 lines
6.8 KiB
TypeScript
226 lines
6.8 KiB
TypeScript
import { afterEach, describe, mock, test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { Op } from 'sequelize';
|
|
|
|
import db from '@/db/models';
|
|
import StaffAwardsReviewsService from '@/services/staff_awards_reviews';
|
|
import { FEATURE_PERMISSIONS } from '@/shared/constants/product-permissions';
|
|
import { ROLE_NAMES, ROLE_SCOPES } from '@/shared/constants/roles';
|
|
import { createTestUser } from '@/test-utils';
|
|
|
|
function permission(name: string) {
|
|
return { name };
|
|
}
|
|
|
|
function isRecord(value: unknown): value is Record<PropertyKey, unknown> {
|
|
return typeof value === 'object' && value !== null;
|
|
}
|
|
|
|
function staffUser(overrides: Record<string, unknown> = {}) {
|
|
const row = {
|
|
id: 'staff-1',
|
|
email: 'teacher@flatlogic.com',
|
|
firstName: 'Emily',
|
|
lastName: 'Johnson',
|
|
organizationId: 'org-1',
|
|
schoolId: 'school-1',
|
|
campusId: 'campus-1',
|
|
app_role: { name: ROLE_NAMES.TEACHER },
|
|
campus: { name: 'Tigers Campus', schoolId: 'school-1' },
|
|
school: { name: 'Demo School' },
|
|
...overrides,
|
|
};
|
|
return {
|
|
...row,
|
|
get: () => row,
|
|
};
|
|
}
|
|
|
|
function awardRecord(overrides: Record<string, unknown> = {}) {
|
|
const row = {
|
|
id: 'record-1',
|
|
title: 'Teacher of the Year',
|
|
record_type: 'award',
|
|
year_label: '2025-2026',
|
|
notes: 'Outstanding classroom management.',
|
|
file_name: null,
|
|
file_url: null,
|
|
file_is_image: false,
|
|
staffUserId: 'staff-1',
|
|
staffUser: staffUser(),
|
|
createdAt: new Date('2026-06-26T12:00:00Z'),
|
|
updatedAt: new Date('2026-06-26T12:00:00Z'),
|
|
...overrides,
|
|
};
|
|
return {
|
|
...row,
|
|
get: () => row,
|
|
};
|
|
}
|
|
|
|
afterEach(() => {
|
|
mock.restoreAll();
|
|
});
|
|
|
|
describe('StaffAwardsReviewsService', () => {
|
|
test('non-managers list only their own awards and reviews', async () => {
|
|
const actor = createTestUser({
|
|
id: 'teacher-1',
|
|
organizationId: 'org-1',
|
|
organizations: { id: 'org-1' },
|
|
campusId: 'campus-1',
|
|
app_role: {
|
|
name: ROLE_NAMES.TEACHER,
|
|
scope: ROLE_SCOPES.CLASS,
|
|
globalAccess: false,
|
|
permissions: [permission(FEATURE_PERMISSIONS.READ_AWARDS_REVIEWS)],
|
|
},
|
|
});
|
|
let capturedWhere: unknown = null;
|
|
|
|
mock.method(db.staff_awards_reviews, 'findAndCountAll', async (options: unknown) => {
|
|
if (isRecord(options)) {
|
|
capturedWhere = options.where;
|
|
}
|
|
return { rows: [awardRecord({ staffUserId: 'teacher-1' })], count: 1 };
|
|
});
|
|
|
|
const result = await StaffAwardsReviewsService.list({ staffUserId: 'other-staff' }, actor);
|
|
|
|
assert.equal(result.canManage, false);
|
|
assert.equal(result.selectedStaffUserId, 'teacher-1');
|
|
assert.equal(isRecord(capturedWhere), true);
|
|
if (isRecord(capturedWhere)) {
|
|
assert.equal(capturedWhere.staffUserId, 'teacher-1');
|
|
assert.equal(capturedWhere.campusId, 'campus-1');
|
|
}
|
|
});
|
|
|
|
test('managers list staff using school descendant scope', async () => {
|
|
const actor = createTestUser({
|
|
id: 'principal-1',
|
|
organizationId: 'org-1',
|
|
organizations: { id: 'org-1' },
|
|
schoolId: 'school-1',
|
|
campusId: null,
|
|
app_role: {
|
|
name: ROLE_NAMES.PRINCIPAL,
|
|
scope: ROLE_SCOPES.SCHOOL,
|
|
globalAccess: false,
|
|
permissions: [
|
|
permission(FEATURE_PERMISSIONS.READ_AWARDS_REVIEWS),
|
|
permission(FEATURE_PERMISSIONS.MANAGE_AWARDS_REVIEWS),
|
|
],
|
|
},
|
|
});
|
|
let capturedWhere: unknown = null;
|
|
|
|
mock.method(db.users, 'findAndCountAll', async (options: unknown) => {
|
|
if (isRecord(options)) {
|
|
capturedWhere = options.where;
|
|
}
|
|
return { rows: [staffUser()], count: 1 };
|
|
});
|
|
|
|
const result = await StaffAwardsReviewsService.listStaff({}, actor);
|
|
|
|
assert.equal(result.count, 1);
|
|
assert.equal(result.rows[0]?.name, 'Emily Johnson');
|
|
assert.equal(isRecord(capturedWhere), true);
|
|
if (isRecord(capturedWhere)) {
|
|
assert.equal(Object.getOwnPropertySymbols(capturedWhere).includes(Op.or), true);
|
|
}
|
|
});
|
|
|
|
test('manager create stores selected staff tenant and campus fields', async () => {
|
|
const actor = createTestUser({
|
|
id: 'director-1',
|
|
organizationId: 'org-1',
|
|
organizations: { id: 'org-1' },
|
|
campusId: 'campus-1',
|
|
app_role: {
|
|
name: ROLE_NAMES.DIRECTOR,
|
|
scope: ROLE_SCOPES.CAMPUS,
|
|
globalAccess: false,
|
|
permissions: [
|
|
permission(FEATURE_PERMISSIONS.READ_AWARDS_REVIEWS),
|
|
permission(FEATURE_PERMISSIONS.MANAGE_AWARDS_REVIEWS),
|
|
],
|
|
},
|
|
});
|
|
let staffWhere: unknown = null;
|
|
let createdPayload: unknown = null;
|
|
|
|
mock.method(db.users, 'findOne', async (options: unknown) => {
|
|
if (isRecord(options)) {
|
|
staffWhere = options.where;
|
|
}
|
|
return staffUser({
|
|
id: 'teacher-1',
|
|
organizationId: 'org-1',
|
|
schoolId: null,
|
|
campusId: 'campus-1',
|
|
campus: { schoolId: 'school-1', name: 'Tigers Campus' },
|
|
});
|
|
});
|
|
mock.method(db.sequelize, 'transaction', async () => ({
|
|
commit: async () => undefined,
|
|
rollback: async () => undefined,
|
|
}));
|
|
mock.method(db.staff_awards_reviews, 'create', async (payload: unknown) => {
|
|
createdPayload = payload;
|
|
return awardRecord(isRecord(payload) ? payload : {});
|
|
});
|
|
|
|
const created = await StaffAwardsReviewsService.create(
|
|
{
|
|
staffUserId: 'teacher-1',
|
|
title: ' Teacher of the Year ',
|
|
type: 'award',
|
|
year: '2025-2026',
|
|
notes: ' Great progress. ',
|
|
fileName: 'award.pdf',
|
|
fileUrl: '/private/files/award.pdf',
|
|
fileIsImage: false,
|
|
},
|
|
actor,
|
|
);
|
|
|
|
assert.equal(created.title, 'Teacher of the Year');
|
|
assert.equal(isRecord(staffWhere), true);
|
|
if (isRecord(staffWhere)) {
|
|
assert.equal(staffWhere.id, 'teacher-1');
|
|
assert.equal(Object.getOwnPropertySymbols(staffWhere).includes(Op.or), true);
|
|
}
|
|
assert.equal(isRecord(createdPayload), true);
|
|
if (isRecord(createdPayload)) {
|
|
assert.equal(createdPayload.organizationId, 'org-1');
|
|
assert.equal(createdPayload.schoolId, 'school-1');
|
|
assert.equal(createdPayload.campusId, 'campus-1');
|
|
assert.equal(createdPayload.staffUserId, 'teacher-1');
|
|
assert.equal(createdPayload.createdById, 'director-1');
|
|
assert.equal(createdPayload.updatedById, 'director-1');
|
|
}
|
|
});
|
|
|
|
test('staff list requires awards/reviews management permission', async () => {
|
|
const actor = createTestUser({
|
|
id: 'teacher-1',
|
|
organizationId: 'org-1',
|
|
organizations: { id: 'org-1' },
|
|
campusId: 'campus-1',
|
|
app_role: {
|
|
name: ROLE_NAMES.TEACHER,
|
|
scope: ROLE_SCOPES.CLASS,
|
|
globalAccess: false,
|
|
permissions: [permission(FEATURE_PERMISSIONS.READ_AWARDS_REVIEWS)],
|
|
},
|
|
});
|
|
|
|
await assert.rejects(
|
|
() => StaffAwardsReviewsService.listStaff({}, actor),
|
|
{ name: 'ForbiddenError' },
|
|
);
|
|
});
|
|
});
|