85 lines
3.1 KiB
JavaScript
85 lines
3.1 KiB
JavaScript
/**
|
|
* Property-Based Test: Profile API Returns Complete User Data Matching Database
|
|
*
|
|
* Feature: user-profile, Property 2: Profile API returns complete user data matching database
|
|
*
|
|
* For any active user record, the profile route's mapping logic produces a
|
|
* response object with all 6 required fields (id, username, email, group,
|
|
* created_at, last_login) and each value matches the corresponding column
|
|
* in the users table. The `group` field maps from the `user_group` column.
|
|
*
|
|
* Validates: Requirements 4.1
|
|
*/
|
|
|
|
const fc = require('fast-check');
|
|
|
|
/**
|
|
* Simulates the exact mapping logic from GET /api/auth/profile in routes/auth.js:
|
|
*
|
|
* res.json({
|
|
* id: user.id,
|
|
* username: user.username,
|
|
* email: user.email,
|
|
* group: user.user_group,
|
|
* created_at: user.created_at,
|
|
* last_login: user.last_login
|
|
* });
|
|
*/
|
|
function mapUserRowToProfileResponse(user) {
|
|
return {
|
|
id: user.id,
|
|
username: user.username,
|
|
email: user.email,
|
|
group: user.user_group,
|
|
created_at: user.created_at,
|
|
last_login: user.last_login
|
|
};
|
|
}
|
|
|
|
describe('Feature: user-profile, Property 2: Profile API returns complete user data matching database', () => {
|
|
it('profile response contains all 6 required fields matching the database row', () => {
|
|
fc.assert(
|
|
fc.property(
|
|
// Generate arbitrary user rows matching the users table schema
|
|
fc.record({
|
|
id: fc.integer({ min: 1, max: 1000000 }),
|
|
username: fc.string({ minLength: 1, maxLength: 50 }),
|
|
email: fc.string({ minLength: 3, maxLength: 255 }),
|
|
user_group: fc.constantFrom('Admin', 'Standard_User', 'Read_Only'),
|
|
created_at: fc.integer({ min: 1577836800000, max: 1924991999000 })
|
|
.map(ts => new Date(ts).toISOString().replace('T', ' ').slice(0, 19)),
|
|
last_login: fc.oneof(
|
|
fc.integer({ min: 1577836800000, max: 1924991999000 })
|
|
.map(ts => new Date(ts).toISOString().replace('T', ' ').slice(0, 19)),
|
|
fc.constant(null)
|
|
),
|
|
is_active: fc.constant(1)
|
|
}),
|
|
(userRow) => {
|
|
const response = mapUserRowToProfileResponse(userRow);
|
|
|
|
// Assert all 6 required fields are present
|
|
expect(response).toHaveProperty('id');
|
|
expect(response).toHaveProperty('username');
|
|
expect(response).toHaveProperty('email');
|
|
expect(response).toHaveProperty('group');
|
|
expect(response).toHaveProperty('created_at');
|
|
expect(response).toHaveProperty('last_login');
|
|
|
|
// Assert each value matches the corresponding database column
|
|
expect(response.id).toBe(userRow.id);
|
|
expect(response.username).toBe(userRow.username);
|
|
expect(response.email).toBe(userRow.email);
|
|
expect(response.group).toBe(userRow.user_group); // group maps from user_group
|
|
expect(response.created_at).toBe(userRow.created_at);
|
|
expect(response.last_login).toBe(userRow.last_login);
|
|
|
|
// Assert exactly 6 keys — no extra fields leaked
|
|
expect(Object.keys(response)).toHaveLength(6);
|
|
}
|
|
),
|
|
{ numRuns: 100 }
|
|
);
|
|
});
|
|
});
|