forked from UKSOURCE/cms.hailearning.edu.vn
first commit
This commit is contained in:
179
tests/api.property.test.js
Normal file
179
tests/api.property.test.js
Normal file
@@ -0,0 +1,179 @@
|
||||
/**
|
||||
* Property-Based Tests for API endpoints
|
||||
* Feature: degree-management-refactor
|
||||
* Uses: fast-check + mongodb-memory-server + jest + supertest
|
||||
*/
|
||||
|
||||
const mongoose = require('mongoose');
|
||||
const { MongoMemoryServer } = require('mongodb-memory-server');
|
||||
const fc = require('fast-check');
|
||||
const express = require('express');
|
||||
const request = require('supertest');
|
||||
|
||||
// Models
|
||||
const Degree = require('../models/degree');
|
||||
const Department = require('../models/department');
|
||||
const Level = require('../models/level');
|
||||
|
||||
// Routes
|
||||
const indexRoutes = require('../routes/index');
|
||||
|
||||
let mongod;
|
||||
let app;
|
||||
|
||||
// Increase global timeout for slow MongoDB startup and PBT runs
|
||||
jest.setTimeout(120000);
|
||||
|
||||
// ─── Setup / Teardown ────────────────────────────────────────────────────────
|
||||
|
||||
beforeAll(async () => {
|
||||
// Set test API key before anything else
|
||||
process.env.API_KEY = 'test-api-key-12345';
|
||||
|
||||
mongod = await MongoMemoryServer.create();
|
||||
const uri = mongod.getUri();
|
||||
await mongoose.connect(uri);
|
||||
|
||||
// Create a minimal Express app that mounts routes/index.js only
|
||||
app = express();
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
app.use('/', indexRoutes);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await mongoose.disconnect();
|
||||
await mongod.stop();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await Degree.deleteMany({});
|
||||
await Department.deleteMany({});
|
||||
await Level.deleteMany({});
|
||||
});
|
||||
|
||||
// ─── Seed Helpers ────────────────────────────────────────────────────────────
|
||||
|
||||
let _counter = 0;
|
||||
function uid() {
|
||||
return `${Date.now()}_${++_counter}_${Math.random().toString(36).slice(2)}`;
|
||||
}
|
||||
|
||||
async function createDept() {
|
||||
const id = uid();
|
||||
return Department.create({ name: `dept_${id}`, slug: `dept-${id}` });
|
||||
}
|
||||
|
||||
async function createLevel() {
|
||||
const id = uid();
|
||||
return Level.create({ type: `level_${id}` });
|
||||
}
|
||||
|
||||
function makeDegreeData(dept, level, overrides = {}) {
|
||||
const id = uid();
|
||||
return {
|
||||
qualification_number: `QN-${id}`,
|
||||
student_name: `Student ${id}`,
|
||||
program_name: `Program ${id}`,
|
||||
type: 'qualification',
|
||||
department: dept._id,
|
||||
level: level._id,
|
||||
issued_date: new Date('2024-01-01'),
|
||||
status: 'active',
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
// ─── Property 15: API lookup trả về đúng thông tin Degree ───────────────────
|
||||
|
||||
// Feature: degree-management-refactor, Property 15: API lookup trả về đúng thông tin Degree
|
||||
test('Property 15: GET /api/degree/:qualificationNumber returns correct Degree fields', async () => {
|
||||
// Validates: Requirements 5.1, 5.2
|
||||
await fc.assert(
|
||||
fc.asyncProperty(
|
||||
fc.record({
|
||||
student_name: fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim() === s && s.trim().length > 0),
|
||||
program_name: fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim() === s && s.trim().length > 0),
|
||||
}),
|
||||
async ({ student_name, program_name }) => {
|
||||
const dept = await createDept();
|
||||
const lvl = await createLevel();
|
||||
const data = makeDegreeData(dept, lvl, { student_name, program_name });
|
||||
|
||||
const saved = await Degree.create(data);
|
||||
|
||||
const res = await request(app)
|
||||
.get(`/api/degree/${saved.qualification_number}`)
|
||||
.set('x-api-key', process.env.API_KEY);
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body.student_name).toBe(student_name);
|
||||
expect(res.body.program_name).toBe(program_name);
|
||||
expect(res.body.type).toBe(data.type);
|
||||
expect(res.body.status).toBe(data.status);
|
||||
expect(res.body.issued_date).toBeDefined();
|
||||
}
|
||||
),
|
||||
{ numRuns: 100 }
|
||||
);
|
||||
}, 120000);
|
||||
|
||||
// ─── Property 16: API không có API key hợp lệ trả về 401 ────────────────────
|
||||
|
||||
// Feature: degree-management-refactor, Property 16: API không có API key hợp lệ trả về 401
|
||||
test('Property 16: requests without valid x-api-key return 401', async () => {
|
||||
// Validates: Requirements 5.3, 5.6
|
||||
await fc.assert(
|
||||
fc.asyncProperty(
|
||||
fc.oneof(
|
||||
fc.constant(null), // no header at all
|
||||
fc.string({ minLength: 1, maxLength: 40 }) // random invalid key
|
||||
.filter(s => s !== process.env.API_KEY)
|
||||
),
|
||||
async (invalidKey) => {
|
||||
const dept = await createDept();
|
||||
const lvl = await createLevel();
|
||||
const data = makeDegreeData(dept, lvl);
|
||||
await Degree.create(data);
|
||||
|
||||
let req = request(app).get(`/api/degree/${data.qualification_number}`);
|
||||
if (invalidKey !== null) {
|
||||
req = req.set('x-api-key', invalidKey);
|
||||
}
|
||||
|
||||
const res = await req;
|
||||
|
||||
expect(res.status).toBe(401);
|
||||
expect(res.body).toEqual({ error: 'Unauthorized - Invalid API key' });
|
||||
}
|
||||
),
|
||||
{ numRuns: 100 }
|
||||
);
|
||||
}, 120000);
|
||||
|
||||
// ─── Property 17: Degree bị thu hồi vẫn trả về với status revoked ───────────
|
||||
|
||||
// Feature: degree-management-refactor, Property 17: Degree bị thu hồi vẫn trả về với status revoked
|
||||
test('Property 17: revoked Degree is returned by API with status "revoked" (not 404)', async () => {
|
||||
// Validates: Requirements 5.5
|
||||
await fc.assert(
|
||||
fc.asyncProperty(
|
||||
fc.constant(null), // no extra input needed
|
||||
async () => {
|
||||
const dept = await createDept();
|
||||
const lvl = await createLevel();
|
||||
const data = makeDegreeData(dept, lvl, { status: 'revoked' });
|
||||
|
||||
const saved = await Degree.create(data);
|
||||
|
||||
const res = await request(app)
|
||||
.get(`/api/degree/${saved.qualification_number}`)
|
||||
.set('x-api-key', process.env.API_KEY);
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body.status).toBe('revoked');
|
||||
}
|
||||
),
|
||||
{ numRuns: 100 }
|
||||
);
|
||||
}, 120000);
|
||||
Reference in New Issue
Block a user