mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-01-23 02:36:05 +00:00
test(server): fix testing gaps and failing tests
1. Add DELETE /api/sync/data route tests to sync.routes.spec.ts:
- Test successful deletion returns { success: true }
- Test 401 without authorization
- Test uploading new data works after reset
2. Fix passkey.spec.ts failures (4 tests):
- Add missing passkey.findUnique mock for credential lookup
- Update test expectations for discoverable credentials
(no allowCredentials - implementation changed)
3. Fix password-reset-api.spec.ts failures (12 tests):
- Exclude from vitest - tests routes that don't exist
- Server uses passkey/magic link auth, not password auth
All 412 tests now pass.
This commit is contained in:
parent
001d89d58e
commit
f0f536671b
3 changed files with 127 additions and 6 deletions
|
|
@ -25,6 +25,7 @@ vi.mock('../src/db', () => {
|
|||
create: vi.fn(),
|
||||
update: vi.fn(),
|
||||
deleteMany: vi.fn(),
|
||||
findUnique: vi.fn(),
|
||||
},
|
||||
$transaction: vi.fn(),
|
||||
};
|
||||
|
|
@ -86,6 +87,7 @@ describe('Passkey Authentication', () => {
|
|||
create: Mock;
|
||||
update: Mock;
|
||||
deleteMany: Mock;
|
||||
findUnique: Mock;
|
||||
};
|
||||
$transaction: Mock;
|
||||
};
|
||||
|
|
@ -281,14 +283,12 @@ describe('Passkey Authentication', () => {
|
|||
|
||||
expect(options).toBeDefined();
|
||||
expect(options.challenge).toBe(testChallenge);
|
||||
// Implementation uses discoverable credentials (no allowCredentials)
|
||||
// to let browser show all available passkeys for this RP
|
||||
expect(mockGenerateAuthentication).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
allowCredentials: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
transports: ['internal'],
|
||||
}),
|
||||
]),
|
||||
rpID: 'localhost',
|
||||
userVerification: 'preferred',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
|
@ -311,6 +311,16 @@ describe('Passkey Authentication', () => {
|
|||
passkeys: [mockPasskey],
|
||||
});
|
||||
|
||||
// Mock passkey lookup by credential ID (new implementation uses discoverable credentials)
|
||||
mockPrisma.passkey.findUnique.mockResolvedValue({
|
||||
...mockPasskey,
|
||||
user: {
|
||||
id: 1,
|
||||
email: testEmail,
|
||||
isVerified: 1,
|
||||
},
|
||||
});
|
||||
|
||||
mockVerifyAuthentication.mockResolvedValue({
|
||||
verified: true,
|
||||
authenticationInfo: {
|
||||
|
|
@ -356,6 +366,16 @@ describe('Passkey Authentication', () => {
|
|||
passkeys: [mockPasskey],
|
||||
});
|
||||
|
||||
// Mock passkey lookup - user is unverified
|
||||
mockPrisma.passkey.findUnique.mockResolvedValue({
|
||||
...mockPasskey,
|
||||
user: {
|
||||
id: 1,
|
||||
email: testEmail,
|
||||
isVerified: 0,
|
||||
},
|
||||
});
|
||||
|
||||
// Generate options first
|
||||
await generateAuthenticationOptions(testEmail);
|
||||
|
||||
|
|
@ -384,6 +404,9 @@ describe('Passkey Authentication', () => {
|
|||
passkeys: [mockPasskey],
|
||||
});
|
||||
|
||||
// Mock passkey lookup returns null (credential not found)
|
||||
mockPrisma.passkey.findUnique.mockResolvedValue(null);
|
||||
|
||||
// Generate options first
|
||||
await generateAuthenticationOptions(testEmail);
|
||||
|
||||
|
|
|
|||
|
|
@ -1440,4 +1440,100 @@ describe('Restore Points API', () => {
|
|||
expect(retryResponse.json().errorCode).toBe('SYNC_IMPORT_EXISTS');
|
||||
});
|
||||
});
|
||||
|
||||
describe('DELETE /api/sync/data - Reset Account (Delete All User Data)', () => {
|
||||
it('should delete all user data and return success', async () => {
|
||||
// First upload some operations
|
||||
await app.inject({
|
||||
method: 'POST',
|
||||
url: '/api/sync/ops',
|
||||
headers: { authorization: `Bearer ${authToken}` },
|
||||
payload: {
|
||||
ops: [
|
||||
createOp(clientId, { entityId: 'task-1' }),
|
||||
createOp(clientId, { entityId: 'task-2' }),
|
||||
],
|
||||
clientId,
|
||||
},
|
||||
});
|
||||
|
||||
// Verify ops exist
|
||||
const opsBefore = await app.inject({
|
||||
method: 'GET',
|
||||
url: '/api/sync/ops?sinceSeq=0',
|
||||
headers: { authorization: `Bearer ${authToken}` },
|
||||
});
|
||||
expect(opsBefore.json().ops.length).toBe(2);
|
||||
|
||||
// Delete all user data
|
||||
const deleteResponse = await app.inject({
|
||||
method: 'DELETE',
|
||||
url: '/api/sync/data',
|
||||
headers: { authorization: `Bearer ${authToken}` },
|
||||
});
|
||||
|
||||
expect(deleteResponse.statusCode).toBe(200);
|
||||
expect(deleteResponse.json()).toEqual({ success: true });
|
||||
|
||||
// Verify ops are gone
|
||||
const opsAfter = await app.inject({
|
||||
method: 'GET',
|
||||
url: '/api/sync/ops?sinceSeq=0',
|
||||
headers: { authorization: `Bearer ${authToken}` },
|
||||
});
|
||||
expect(opsAfter.json().ops.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should return 401 without authorization', async () => {
|
||||
const response = await app.inject({
|
||||
method: 'DELETE',
|
||||
url: '/api/sync/data',
|
||||
});
|
||||
|
||||
expect(response.statusCode).toBe(401);
|
||||
});
|
||||
|
||||
it('should allow uploading new data after reset', async () => {
|
||||
// Upload initial data
|
||||
await app.inject({
|
||||
method: 'POST',
|
||||
url: '/api/sync/ops',
|
||||
headers: { authorization: `Bearer ${authToken}` },
|
||||
payload: {
|
||||
ops: [createOp(clientId, { entityId: 'old-task' })],
|
||||
clientId,
|
||||
},
|
||||
});
|
||||
|
||||
// Delete all data
|
||||
await app.inject({
|
||||
method: 'DELETE',
|
||||
url: '/api/sync/data',
|
||||
headers: { authorization: `Bearer ${authToken}` },
|
||||
});
|
||||
|
||||
// Upload new data after reset
|
||||
const uploadResponse = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/api/sync/ops',
|
||||
headers: { authorization: `Bearer ${authToken}` },
|
||||
payload: {
|
||||
ops: [createOp(clientId, { entityId: 'new-task' })],
|
||||
clientId,
|
||||
},
|
||||
});
|
||||
|
||||
expect(uploadResponse.statusCode).toBe(200);
|
||||
expect(uploadResponse.json().results[0].accepted).toBe(true);
|
||||
|
||||
// Verify only new data exists
|
||||
const ops = await app.inject({
|
||||
method: 'GET',
|
||||
url: '/api/sync/ops?sinceSeq=0',
|
||||
headers: { authorization: `Bearer ${authToken}` },
|
||||
});
|
||||
expect(ops.json().ops.length).toBe(1);
|
||||
expect(ops.json().ops[0].entityId).toBe('new-task');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ export default defineConfig({
|
|||
'tests/snapshot-skip-optimization.spec.ts',
|
||||
'tests/integration/multi-client-sync.integration.spec.ts',
|
||||
'tests/integration/snapshot-skip-optimization.integration.spec.ts',
|
||||
// Tests password reset routes that don't exist - server uses passkey/magic link auth
|
||||
'tests/password-reset-api.spec.ts',
|
||||
],
|
||||
},
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue