Files
ProjectE/apps/web/__tests__/lib/auth.test.ts
T
Hermes c3bce0b9e3 fix(p0): resolve active domain automatically when onboarding skipped
- New resolveActiveDomain() helper in apps/web/lib/auth.ts: returns the
  user's first existing domain (ordered by sort_order then created_at),
  or auto-creates a default 'Personal' domain if they have none.
- 7 affected API routes (agents, canvases, domains, habits, projects,
  search, tasks) now fall back to resolveActiveDomain when the request
  omits a domain param. This eliminates the UNDEFINED_VALUE on domain_id
  and 22P02 invalid uuid errors that broke 6+ UI flows.
- New POST /api/quick-capture route: switches on type=task|habit|note|project
  to create the right entity, after resolving the active domain. This is
  the API the dashboard quick-capture widget calls.
- packages/db/src/schema.ts: added ownerId: uuid('owner_id') to the
  domains table so each user owns their domains.
- drizzle/0004_add_owner_id_to_domains.sql: matching migration with
  column add + index.
- apps/web/__tests__/lib/auth.test.ts: unit tests for both branches of
  resolveActiveDomain (returns existing / creates Personal).
- apps/web/app/(dashboard)/canvas/page.tsx: removed hardcoded
  domain: 'personal' from the quick-create payload so server-side
  resolution can do its job.

This unblocks all of: /tasks, /habits, /projects, /search, /settings/agents,
/settings/domains, /canvas, and dashboard quick-capture.
2026-07-30 23:51:29 +00:00

84 lines
2.9 KiB
TypeScript

/**
* Unit tests for resolveActiveDomain helper in lib/auth.ts.
* Tests both branches: existing domain returned, and auto-creation of "Personal" domain.
*/
import { describe, it, expect, jest, beforeEach } from '@jest/globals';
// Mock the database module
const mockDb = {
select: jest.fn(),
insert: jest.fn(),
};
const mockDomains = {};
jest.mock('@project-e/db', () => ({
db: mockDb,
domains: mockDomains,
}));
// Mock next-auth
jest.mock('next-auth', () => ({
getServerSession: jest.fn(),
}));
// Mock next-auth config
jest.mock('@/lib/auth-config', () => ({
authOptions: {},
}));
describe('resolveActiveDomain', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('should return the user\'s first existing domain without creating one', async () => {
const { resolveActiveDomain } = await import('@/lib/auth');
const mockUser = { id: 'user-1', email: 'test@example.com', name: 'Test' };
const mockDomain = { id: 'domain-1', name: 'Work' };
// Mock the select chain to return an existing domain
const mockLimit = jest.fn().mockResolvedValue([mockDomain]);
const mockOrderBy = jest.fn().mockReturnValue({ limit: mockLimit });
const mockWhere = jest.fn().mockReturnValue({ orderBy: mockOrderBy });
const mockFrom = jest.fn().mockReturnValue({ where: mockWhere });
mockDb.select.mockReturnValue({ from: mockFrom });
const result = await resolveActiveDomain(mockUser);
expect(result).toEqual({ id: 'domain-1', name: 'Work', created: false });
expect(mockDb.select).toHaveBeenCalledWith({ id: expect.anything(), name: expect.anything() });
expect(mockDb.insert).not.toHaveBeenCalled();
});
it('should create a "Personal" domain when the user has none', async () => {
const { resolveActiveDomain } = await import('@/lib/auth');
const mockUser = { id: 'user-2', email: 'new@example.com', name: 'New User' };
const mockCreatedDomain = { id: 'new-domain-id', name: 'Personal' };
// First call: no existing domain
const mockLimit1 = jest.fn().mockResolvedValue([]);
const mockOrderBy1 = jest.fn().mockReturnValue({ limit: mockLimit1 });
const mockWhere1 = jest.fn().mockReturnValue({ orderBy: mockOrderBy1 });
const mockFrom1 = jest.fn().mockReturnValue({ where: mockWhere1 });
mockDb.select.mockReturnValue({ from: mockFrom1 });
// Insert returns the created domain
const mockReturning = jest.fn().mockResolvedValue([mockCreatedDomain]);
const mockValues = jest.fn().mockReturnValue({ returning: mockReturning });
mockDb.insert.mockReturnValue({ values: mockValues });
const result = await resolveActiveDomain(mockUser);
expect(result).toEqual({ id: 'new-domain-id', name: 'Personal', created: true });
expect(mockDb.insert).toHaveBeenCalled();
expect(mockValues).toHaveBeenCalledWith(expect.objectContaining({
ownerId: 'user-2',
name: 'Personal',
sortOrder: 0,
}));
});
});