manicanldes-backend/test/migration-integrity.spec.ts

100 lines
3.0 KiB
TypeScript

import { identityApp, type IdentityApp } from './helpers/identity-app';
import { legacyOrganizationId } from './helpers/test-database';
import { RecoveryStore } from '../src/identity/recovery.store';
import { issueToken } from '../src/identity/tokens';
describe('migration integrity', () => {
let ctx: IdentityApp;
beforeAll(async () => {
ctx = await identityApp();
}, 60000);
afterAll(async () => {
await ctx?.close();
});
it('preserves organization rows from the foundation migration', async () => {
expect(
await ctx.db.organization.findUnique({
where: { id: legacyOrganizationId },
}),
).toMatchObject({ name: 'Existing organization' });
});
it('enforces normalized emails in the database', async () => {
await expect(
ctx.db.user.create({
data: {
organizationId: ctx.owner.organizationId,
email: 'UPPER@example.com',
name: 'Bad',
passwordHash: 'hash',
},
}),
).rejects.toThrow();
});
it('prevents a second owner even when the API is bypassed', async () => {
await expect(
ctx.db.user.create({
data: {
organizationId: ctx.owner.organizationId,
email: 'second@example.com',
name: 'Second',
passwordHash: 'hash',
isOwner: true,
},
}),
).rejects.toThrow();
});
it('rejects audit modification and deletion', async () => {
const audit = await ctx.db.auditEvent.findFirstOrThrow();
await expect(
ctx.db.auditEvent.update({
where: { id: audit.id },
data: { action: 'tampered' },
}),
).rejects.toThrow();
await expect(
ctx.db.auditEvent.delete({ where: { id: audit.id } }),
).rejects.toThrow();
expect(
await ctx.db.auditEvent.findUnique({ where: { id: audit.id } }),
).not.toBeNull();
});
it('discards failed-delivery recovery tokens', async () => {
const token = issueToken();
const store = ctx.app.get(RecoveryStore);
await store.create(
ctx.owner.userId,
token.tokenHash,
new Date(Date.now() + 60000),
);
await store.discard(token.tokenHash);
expect(
await ctx.db.recoveryToken.findUnique({
where: { tokenHash: token.tokenHash },
}),
).toBeNull();
});
const nativeOnly = process.env.TEST_DATABASE_URL ? it : it.skip;
nativeOnly(
'allows exactly one winner for concurrent recovery token consumption on native PostgreSQL',
async () => {
const token = issueToken();
const store = ctx.app.get(RecoveryStore);
await store.create(
ctx.owner.userId,
token.tokenHash,
new Date(Date.now() + 60000),
);
const results = await Promise.allSettled([
store.reset(token.tokenHash, 'hash-one'),
store.reset(token.tokenHash, 'hash-two'),
]);
expect(
results.filter((result) => result.status === 'fulfilled'),
).toHaveLength(1);
expect(
results.filter((result) => result.status === 'rejected'),
).toHaveLength(1);
},
);
});