update first - 84
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import request from 'supertest';
|
||||
import { createTestApp, loginToken } from './helpers.js';
|
||||
|
||||
test('HTTP auth, role filtering, validation, and progress update are real', async () => {
|
||||
const { app, prisma } = await createTestApp();
|
||||
try {
|
||||
await request(app.getHttpServer()).get('/api/v1/objectives').expect(401);
|
||||
|
||||
const employeeToken = await loginToken(app, 'employee');
|
||||
const managerToken = await loginToken(app, 'manager');
|
||||
|
||||
const employeeObjectives = await request(app.getHttpServer())
|
||||
.get('/api/v1/objectives')
|
||||
.set('Authorization', `Bearer ${employeeToken}`)
|
||||
.expect(200);
|
||||
assert.equal(employeeObjectives.body.success, true);
|
||||
assert.deepEqual(
|
||||
employeeObjectives.body.data.map((objective: { ownerId: number }) => objective.ownerId),
|
||||
[3],
|
||||
);
|
||||
|
||||
const managerObjectives = await request(app.getHttpServer())
|
||||
.get('/api/v1/objectives')
|
||||
.set('Authorization', `Bearer ${managerToken}`)
|
||||
.expect(200);
|
||||
assert.equal(managerObjectives.body.data.length, 3);
|
||||
|
||||
await request(app.getHttpServer())
|
||||
.post('/api/v1/objectives')
|
||||
.set('Authorization', `Bearer ${employeeToken}`)
|
||||
.send({ title: '', ownerId: 3, quarter: '2026-Q2' })
|
||||
.expect(400);
|
||||
|
||||
const progressResponse = await request(app.getHttpServer())
|
||||
.patch('/api/v1/key-results/2/progress')
|
||||
.set('Authorization', `Bearer ${employeeToken}`)
|
||||
.send({ progress: 70, comment: 'Updated in e2e test' })
|
||||
.expect(200);
|
||||
assert.equal(progressResponse.body.data.progress, 70);
|
||||
await prisma.keyResult.update({ where: { id: 2 }, data: { progress: 60 } });
|
||||
await prisma.progressUpdate.deleteMany({ where: { keyResultId: 2, progress: 70 } });
|
||||
} finally {
|
||||
await app.close();
|
||||
}
|
||||
});
|
||||
|
||||
test('golden objective list response does not drift', async () => {
|
||||
const { app } = await createTestApp();
|
||||
try {
|
||||
const token = await loginToken(app, 'manager');
|
||||
const response = await request(app.getHttpServer())
|
||||
.get('/api/v1/objectives')
|
||||
.set('Authorization', `Bearer ${token}`)
|
||||
.expect(200);
|
||||
const canonical = JSON.stringify(
|
||||
{
|
||||
success: response.body.success,
|
||||
data: response.body.data.map((objective: { id: number; title: string; quarter: string; status: string; keyResults: { id: number; title: string; progress: number }[] }) => ({
|
||||
id: objective.id,
|
||||
title: objective.title,
|
||||
quarter: objective.quarter,
|
||||
status: objective.status,
|
||||
keyResults: objective.keyResults.map((keyResult) => ({
|
||||
id: keyResult.id,
|
||||
title: keyResult.title,
|
||||
progress: keyResult.progress,
|
||||
})),
|
||||
})),
|
||||
meta: response.body.meta,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
);
|
||||
const fixture = await fs.readFile(path.resolve('test/golden/objectives.manager.json'), 'utf8');
|
||||
assert.equal(`${canonical}\n`, fixture);
|
||||
} finally {
|
||||
await app.close();
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "POC AI for SQL Injection prevention",
|
||||
"quarter": "Q2/2026",
|
||||
"status": "IN_PROGRESS",
|
||||
"keyResults": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "Complete 3 POC sessions with security team",
|
||||
"progress": 33
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"title": "Reduce manual SQL injection review effort by 30%",
|
||||
"progress": 60
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"title": "AI for All enablement across department",
|
||||
"quarter": "Q2/2026",
|
||||
"status": "NOT_STARTED",
|
||||
"keyResults": [
|
||||
{
|
||||
"id": 3,
|
||||
"title": "Certify 100 department members on AI for All",
|
||||
"progress": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"title": "Improve OKR operating cadence",
|
||||
"quarter": "Q2/2026",
|
||||
"status": "IN_PROGRESS",
|
||||
"keyResults": [
|
||||
{
|
||||
"id": 4,
|
||||
"title": "Reach 90% weekly OKR update compliance",
|
||||
"progress": 75
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"title": "Resolve stale OKR reports within two business days",
|
||||
"progress": 45
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"total": 3
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { ValidationPipe } from '@nestjs/common';
|
||||
import { Test } from '@nestjs/testing';
|
||||
import type { INestApplication } from '@nestjs/common';
|
||||
import { PrismaService } from '../src/prisma/prisma.service.js';
|
||||
import { AppModule } from '../src/app.module.js';
|
||||
|
||||
export async function createTestApp(): Promise<{ app: INestApplication; prisma: PrismaService }> {
|
||||
const moduleRef = await Test.createTestingModule({ imports: [AppModule] }).compile();
|
||||
const app = moduleRef.createNestApplication();
|
||||
app.setGlobalPrefix('api/v1');
|
||||
app.useGlobalPipes(
|
||||
new ValidationPipe({
|
||||
whitelist: true,
|
||||
forbidNonWhitelisted: true,
|
||||
transform: true,
|
||||
}),
|
||||
);
|
||||
await app.init();
|
||||
return { app, prisma: app.get(PrismaService) };
|
||||
}
|
||||
|
||||
export async function loginToken(app: INestApplication, username = 'employee'): Promise<string> {
|
||||
const request = await import('supertest');
|
||||
const response = await request
|
||||
.default(app.getHttpServer())
|
||||
.post('/api/v1/auth/login')
|
||||
.send({ username, password: 'Password@123' })
|
||||
.expect(201);
|
||||
return response.body.data.token as string;
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { ForbiddenException, UnauthorizedException } from '@nestjs/common';
|
||||
import { AuthService } from '../src/auth/auth.service.js';
|
||||
import type { JwtUser } from '../src/common/auth.types.js';
|
||||
import { KeyResultsService } from '../src/key-results/key-results.service.js';
|
||||
import { ObjectivesService } from '../src/objectives/objectives.service.js';
|
||||
import { PrismaService } from '../src/prisma/prisma.service.js';
|
||||
|
||||
const employeeUser: JwtUser = {
|
||||
sub: 3,
|
||||
email: 'employee@okr.local',
|
||||
role: 'EMPLOYEE',
|
||||
name: 'Nguyen Van A',
|
||||
};
|
||||
|
||||
const managerUser: JwtUser = {
|
||||
sub: 2,
|
||||
email: 'manager@okr.local',
|
||||
role: 'MANAGER',
|
||||
name: 'Nguyen Van Manager',
|
||||
};
|
||||
|
||||
test('AuthService rejects invalid passwords and signs valid users', async () => {
|
||||
const prisma = new PrismaService();
|
||||
await prisma.$connect();
|
||||
const service = new AuthService(prisma, new JwtService({ secret: 'test-secret' }));
|
||||
await assert.rejects(() => service.login('employee', 'wrong-password'), UnauthorizedException);
|
||||
const result = await service.login('employee', 'Password@123');
|
||||
assert.equal(result.user.email, 'employee@okr.local');
|
||||
assert.ok(result.token.length > 20);
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
|
||||
test('ObjectivesService applies employee role filtering', async () => {
|
||||
const prisma = new PrismaService();
|
||||
await prisma.$connect();
|
||||
const service = new ObjectivesService(prisma);
|
||||
const employeeObjectives = await service.list(employeeUser);
|
||||
const managerObjectives = await service.list(managerUser);
|
||||
assert.deepEqual(employeeObjectives.map((objective) => objective.ownerId), [employeeUser.sub]);
|
||||
assert.ok(managerObjectives.length > employeeObjectives.length);
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
|
||||
test('KeyResultsService blocks employee updates to another owner and recalculates owned progress', async () => {
|
||||
const prisma = new PrismaService();
|
||||
await prisma.$connect();
|
||||
const service = new KeyResultsService(prisma);
|
||||
await assert.rejects(
|
||||
() => service.updateProgress(3, { progress: 50, comment: 'Not mine' }, employeeUser),
|
||||
ForbiddenException,
|
||||
);
|
||||
const updated = await service.updateProgress(1, { progress: 100, comment: 'Completed' }, employeeUser);
|
||||
assert.equal(updated.progress, 100);
|
||||
const objective = await prisma.objective.findUniqueOrThrow({ where: { id: updated.objectiveId } });
|
||||
assert.equal(objective.status, 'IN_PROGRESS');
|
||||
await prisma.keyResult.update({ where: { id: 1 }, data: { progress: 33 } });
|
||||
await prisma.progressUpdate.deleteMany({ where: { keyResultId: 1 } });
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
Reference in New Issue
Block a user