[grade=B] test: sync auth and version contracts
This commit is contained in:
@@ -4,8 +4,8 @@
|
||||
* Covers the BACKLOG.md DC-006 acceptance criteria:
|
||||
* - no code → 400 (ValidationError)
|
||||
* - wrong code → 401 (AuthenticationError)
|
||||
* - valid TOTP → 200 + session cookie + CSRF token
|
||||
* - check-session with valid session → 200 { authenticated: true }
|
||||
* - valid TOTP → 200 + session cookie + CSRF token + SSO handoff token
|
||||
* - check-session with valid session → 200 { success: true, authenticated: true }
|
||||
* - check-session without session → 401 (AuthenticationError)
|
||||
*
|
||||
* Uses real otplib for code generation (so we exercise the actual TOTP math)
|
||||
@@ -79,6 +79,7 @@ function createApp(depsOverride = {}) {
|
||||
sessionStore.delete(ip);
|
||||
}),
|
||||
clearCookie: jest.fn(),
|
||||
createHandoffToken: jest.fn(() => 'mock-sso-handoff-token'),
|
||||
isValid: jest.fn((req) => {
|
||||
const ip = session.getClientIP(req);
|
||||
const entry = sessionStore.get(ip);
|
||||
@@ -303,8 +304,10 @@ describe('TOTP Auth Routes — DC-006 Integration Test', () => {
|
||||
expect(res.body.success).toBe(true);
|
||||
expect(res.body.message).toMatch(/Authenticated successfully/);
|
||||
expect(res.body.csrfToken).toBe('mock-csrf-token');
|
||||
expect(res.body.ssoToken).toBe('mock-sso-handoff-token');
|
||||
expect(deps.session.create).toHaveBeenCalled();
|
||||
expect(deps.session.setCookie).toHaveBeenCalled();
|
||||
expect(deps.session.createHandoffToken).toHaveBeenCalledTimes(1);
|
||||
expect(deps.renewCSRFToken).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -350,7 +353,7 @@ describe('TOTP Auth Routes — DC-006 Integration Test', () => {
|
||||
deps.session._grantSession('127.0.0.1');
|
||||
const res = await request(app).get('/api/totp/check-session').set('X-Forwarded-For', '127.0.0.1');
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body).toEqual({ authenticated: true });
|
||||
expect(res.body).toEqual({ success: true, authenticated: true });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -450,24 +453,23 @@ describe('TOTP Auth Routes — DC-006 Integration Test', () => {
|
||||
const loginRes = await request(app).post('/api/totp/verify').send({ code: loginCode });
|
||||
expect(loginRes.status).toBe(200);
|
||||
expect(loginRes.body.csrfToken).toBeDefined();
|
||||
expect(loginRes.body.ssoToken).toBe('mock-sso-handoff-token');
|
||||
expect(deps.session.createHandoffToken).toHaveBeenCalledTimes(1);
|
||||
|
||||
// 5. Check-session — should now be authenticated (the BACKLOG "→ endpoint succeeds" step)
|
||||
const checkRes = await request(app).get('/api/totp/check-session');
|
||||
expect(checkRes.status).toBe(200);
|
||||
expect(checkRes.body).toEqual({ authenticated: true });
|
||||
expect(checkRes.body).toEqual({ success: true, authenticated: true });
|
||||
|
||||
// 6. Logout / disable
|
||||
const disableCode = authenticator.generate(secret);
|
||||
const disableRes = await request(app).post('/api/totp/disable').send({ code: disableCode });
|
||||
expect(disableRes.status).toBe(200);
|
||||
|
||||
// 7. After disable, check-session should be 401 (bypass removed for security)
|
||||
// unless the user still holds a valid session, in which case it's 200.
|
||||
// The login step (4) may or may not have granted one depending on test order.
|
||||
// 7. After disable, check-session deterministically rejects before
|
||||
// checking session validity because TOTP protection is disabled.
|
||||
const afterRes = await request(app).get('/api/totp/check-session');
|
||||
// After disable, TOTP is off AND we may or may not have an active session.
|
||||
// The new contract: bypass is gone, but a valid session still authenticates.
|
||||
expect([200, 401]).toContain(afterRes.status);
|
||||
expect(afterRes.status).toBe(401);
|
||||
});
|
||||
|
||||
it('proves otplib is real (not stubbed) by using a totally bogus code', async () => {
|
||||
|
||||
@@ -78,23 +78,19 @@ describe('SelfUpdater.getLocalVersion() — DC-033 regression', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('commit is a git SHA (7-40 hex chars), not null', () => {
|
||||
test('commit contains a git SHA and is not null', () => {
|
||||
expect(result.commit).not.toBeNull();
|
||||
expect(result.commit).toMatch(/^[0-9a-f]{7,40}$/);
|
||||
expect(result.commit).toMatch(/(?:^|-)[0-9a-f]{7,40}$/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('candidate-path resolution survives missing sibling files', () => {
|
||||
// If we shadow __dirname by requiring the module through a different
|
||||
// require() chain, the function should still find package.json via its
|
||||
// candidate-list fallback. This catches the case where someone refactors
|
||||
// the file to a deeper subdirectory and forgets to update the candidates.
|
||||
test('getLocalVersion works regardless of how the module is required', () => {
|
||||
describe('repeat construction uses the same resolved metadata', () => {
|
||||
test('a second instance resolves the same non-fallback version metadata', () => {
|
||||
const mod = require(path.join(API_ROOT, 'src', 'docker', 'self-updater.js'));
|
||||
const Cls = mod.SelfUpdater || mod.default || mod;
|
||||
const result = new Cls({}).getLocalVersion();
|
||||
expect(result.version).not.toBe('0.0.0');
|
||||
expect(result.commit).toMatch(/^[0-9a-f]{7,40}$/);
|
||||
expect(result.commit).toMatch(/(?:^|-)[0-9a-f]{7,40}$/);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user