mirror of
https://github.com/bretzel-app/crumbs.git
synced 2026-10-12 20:06:57 +02:00
- notes-service.test.ts (24 tests): listNotes, getNote, createNote, updateNote, deleteNote, searchNotes, reorderNotes with owner/collaborator patterns - api-utils.test.ts (10 tests): canAccessNote, requireNoteAccess, requireNoteOwnership, getUserId - rate-limit.test.ts (8 tests): checkRateLimit, recordLoginAttempt, clearOldAttempts - tags.test.ts (6 tests): syncNoteTags, fetchTagsForNotes - collaborators.test.ts (4 tests): fetchCollaboratorsForNotes, addCollaborator, removeCollaborator Also fixes test-helpers.ts missing thumbnail_path/featured columns and rate-limit.ts raw SQL Date binding issue. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
101 lines
3.1 KiB
TypeScript
101 lines
3.1 KiB
TypeScript
import { describe, it, expect, beforeEach } from 'vitest';
|
|
import { createTestDb } from './db/test-helpers.js';
|
|
import { notes, noteCollaborators, users } from './db/schema.js';
|
|
import type { Db } from './db/index.js';
|
|
import { canAccessNote, requireNoteAccess, requireNoteOwnership, getUserId } from './api-utils.js';
|
|
|
|
let db: Db;
|
|
const OWNER_ID = 1;
|
|
const COLLAB_ID = 2;
|
|
const STRANGER_ID = 3;
|
|
|
|
function seedUsers() {
|
|
db.insert(users)
|
|
.values([
|
|
{ email: 'owner@test.com', displayName: 'Owner', role: 'admin', authProvider: 'password', createdAt: new Date() },
|
|
{ email: 'collab@test.com', displayName: 'Collab', role: 'user', authProvider: 'password', createdAt: new Date() },
|
|
{ email: 'stranger@test.com', displayName: 'Stranger', role: 'user', authProvider: 'password', createdAt: new Date() }
|
|
])
|
|
.run();
|
|
}
|
|
|
|
function seedNote(id: string) {
|
|
db.insert(notes)
|
|
.values({ id, userId: OWNER_ID, title: 'Test', content: '', createdAt: new Date(), updatedAt: new Date() })
|
|
.run();
|
|
}
|
|
|
|
function shareNote(noteId: string) {
|
|
db.insert(noteCollaborators)
|
|
.values({ noteId, userId: COLLAB_ID, addedBy: OWNER_ID, addedAt: new Date() })
|
|
.run();
|
|
}
|
|
|
|
beforeEach(() => {
|
|
const testDb = createTestDb();
|
|
db = testDb.db;
|
|
seedUsers();
|
|
});
|
|
|
|
describe('canAccessNote', () => {
|
|
it('should allow owner to access their note', () => {
|
|
seedNote('n1');
|
|
const result = canAccessNote(db, 'n1', OWNER_ID);
|
|
expect(result).toEqual({ canAccess: true, isOwner: true });
|
|
});
|
|
|
|
it('should allow collaborator to access shared note', () => {
|
|
seedNote('n1');
|
|
shareNote('n1');
|
|
const result = canAccessNote(db, 'n1', COLLAB_ID);
|
|
expect(result).toEqual({ canAccess: true, isOwner: false });
|
|
});
|
|
|
|
it('should deny non-collaborator access', () => {
|
|
seedNote('n1');
|
|
const result = canAccessNote(db, 'n1', STRANGER_ID);
|
|
expect(result).toEqual({ canAccess: false, isOwner: false });
|
|
});
|
|
|
|
it('should return false for non-existent note', () => {
|
|
const result = canAccessNote(db, 'nonexistent', OWNER_ID);
|
|
expect(result).toEqual({ canAccess: false, isOwner: false });
|
|
});
|
|
});
|
|
|
|
describe('requireNoteAccess', () => {
|
|
it('should throw 404 for unauthorized user', () => {
|
|
seedNote('n1');
|
|
expect(() => requireNoteAccess(db, 'n1', STRANGER_ID)).toThrow();
|
|
});
|
|
|
|
it('should return isOwner for authorized owner', () => {
|
|
seedNote('n1');
|
|
expect(requireNoteAccess(db, 'n1', OWNER_ID)).toEqual({ isOwner: true });
|
|
});
|
|
});
|
|
|
|
describe('requireNoteOwnership', () => {
|
|
it('should throw 404 for collaborator', () => {
|
|
seedNote('n1');
|
|
shareNote('n1');
|
|
expect(() => requireNoteOwnership(db, 'n1', COLLAB_ID)).toThrow();
|
|
});
|
|
|
|
it('should not throw for owner', () => {
|
|
seedNote('n1');
|
|
expect(() => requireNoteOwnership(db, 'n1', OWNER_ID)).not.toThrow();
|
|
});
|
|
});
|
|
|
|
describe('getUserId', () => {
|
|
it('should throw 401 when no user in locals', () => {
|
|
expect(() => getUserId({ locals: {} as App.Locals })).toThrow();
|
|
});
|
|
|
|
it('should return user id from locals', () => {
|
|
const id = getUserId({ locals: { user: { id: 42, role: 'user', email: '', displayName: '' } } as App.Locals });
|
|
expect(id).toBe(42);
|
|
});
|
|
});
|