Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM, PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source files covering clients, berths, interests/pipeline, documents/EOI, expenses/invoices, email, notifications, dashboard, admin, and client portal. CI/CD via Gitea Actions with Docker builds. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
317
tests/helpers/factories.ts
Normal file
317
tests/helpers/factories.ts
Normal file
@@ -0,0 +1,317 @@
|
||||
/**
|
||||
* Test factory helpers.
|
||||
* These return plain data objects — NOT database-inserted records.
|
||||
* Safe to use whether or not a database is available.
|
||||
*/
|
||||
|
||||
// ─── Client ──────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface ClientData {
|
||||
id: string;
|
||||
portId: string;
|
||||
fullName: string;
|
||||
companyName: string | null;
|
||||
nationality: string | null;
|
||||
isProxy: boolean;
|
||||
source: string | null;
|
||||
yachtLengthFt: string | null;
|
||||
yachtLengthM: string | null;
|
||||
archivedAt: Date | null;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export function makeClient(overrides?: Partial<ClientData>): ClientData {
|
||||
return {
|
||||
id: crypto.randomUUID(),
|
||||
portId: crypto.randomUUID(),
|
||||
fullName: 'Test Client',
|
||||
companyName: null,
|
||||
nationality: null,
|
||||
isProxy: false,
|
||||
source: 'manual',
|
||||
yachtLengthFt: null,
|
||||
yachtLengthM: null,
|
||||
archivedAt: null,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
// ─── Interest ─────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface InterestData {
|
||||
id: string;
|
||||
portId: string;
|
||||
clientId: string;
|
||||
berthId: string | null;
|
||||
pipelineStage: string;
|
||||
leadCategory: string | null;
|
||||
source: string | null;
|
||||
eoiStatus: string | null;
|
||||
contractStatus: string | null;
|
||||
depositStatus: string | null;
|
||||
notes: string | null;
|
||||
archivedAt: Date | null;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export function makeInterest(overrides?: Partial<InterestData>): InterestData {
|
||||
return {
|
||||
id: crypto.randomUUID(),
|
||||
portId: crypto.randomUUID(),
|
||||
clientId: crypto.randomUUID(),
|
||||
berthId: null,
|
||||
pipelineStage: 'open',
|
||||
leadCategory: null,
|
||||
source: 'manual',
|
||||
eoiStatus: null,
|
||||
contractStatus: null,
|
||||
depositStatus: null,
|
||||
notes: null,
|
||||
archivedAt: null,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
// ─── Berth ────────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface BerthData {
|
||||
id: string;
|
||||
portId: string;
|
||||
mooringNumber: string;
|
||||
status: string;
|
||||
area: string | null;
|
||||
lengthM: string | null;
|
||||
price: string | null;
|
||||
tenureType: string | null;
|
||||
archivedAt: Date | null;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export function makeBerth(overrides?: Partial<BerthData>): BerthData {
|
||||
return {
|
||||
id: crypto.randomUUID(),
|
||||
portId: crypto.randomUUID(),
|
||||
mooringNumber: `B-${Math.floor(Math.random() * 999) + 1}`,
|
||||
status: 'available',
|
||||
area: null,
|
||||
lengthM: '12',
|
||||
price: '50000',
|
||||
tenureType: 'freehold',
|
||||
archivedAt: null,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
// ─── Webhook ──────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface WebhookData {
|
||||
id: string;
|
||||
portId: string;
|
||||
name: string;
|
||||
url: string;
|
||||
secret: string | null;
|
||||
events: string[];
|
||||
isActive: boolean;
|
||||
createdBy: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export function makeWebhook(overrides?: Partial<WebhookData>): WebhookData {
|
||||
return {
|
||||
id: crypto.randomUUID(),
|
||||
portId: crypto.randomUUID(),
|
||||
name: 'Test Webhook',
|
||||
url: 'https://example.com/webhook',
|
||||
secret: null,
|
||||
events: ['client.created'],
|
||||
isActive: true,
|
||||
createdBy: crypto.randomUUID(),
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
// ─── Audit Log ────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface AuditMeta {
|
||||
userId: string;
|
||||
portId: string;
|
||||
ipAddress: string;
|
||||
userAgent: string;
|
||||
}
|
||||
|
||||
export function makeAuditMeta(overrides?: Partial<AuditMeta>): AuditMeta {
|
||||
return {
|
||||
userId: crypto.randomUUID(),
|
||||
portId: crypto.randomUUID(),
|
||||
ipAddress: '127.0.0.1',
|
||||
userAgent: 'vitest/1.0',
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
// ─── Auth Context ─────────────────────────────────────────────────────────────
|
||||
|
||||
import type { RolePermissions } from '@/lib/db/schema/users';
|
||||
|
||||
/** Full permissions — every action allowed. */
|
||||
export function makeFullPermissions(): RolePermissions {
|
||||
return {
|
||||
clients: { view: true, create: true, edit: true, delete: true, merge: true, export: true },
|
||||
interests: { view: true, create: true, edit: true, delete: true, change_stage: true, generate_eoi: true, export: true },
|
||||
berths: { view: true, edit: true, import: true, manage_waiting_list: true },
|
||||
documents: { view: true, create: true, send_for_signing: true, upload_signed: true, delete: true },
|
||||
expenses: { view: true, create: true, edit: true, delete: true, export: true, scan_receipt: true },
|
||||
invoices: { view: true, create: true, edit: true, delete: true, send: true, record_payment: true, export: true },
|
||||
files: { view: true, upload: true, delete: true, manage_folders: true },
|
||||
email: { view: true, send: true, configure_account: true },
|
||||
reminders: { view_own: true, view_all: true, create: true, edit_own: true, edit_all: true, assign_others: true },
|
||||
calendar: { connect: true, view_events: true },
|
||||
reports: { view_dashboard: true, view_analytics: true, export: true },
|
||||
document_templates: { view: true, generate: true, manage: true },
|
||||
admin: {
|
||||
manage_users: true,
|
||||
view_audit_log: true,
|
||||
manage_settings: true,
|
||||
manage_webhooks: true,
|
||||
manage_reports: true,
|
||||
manage_custom_fields: true,
|
||||
manage_forms: true,
|
||||
manage_tags: true,
|
||||
system_backup: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** Read-only viewer permissions — no create/update/delete. */
|
||||
export function makeViewerPermissions(): RolePermissions {
|
||||
return {
|
||||
clients: { view: true, create: false, edit: false, delete: false, merge: false, export: false },
|
||||
interests: { view: true, create: false, edit: false, delete: false, change_stage: false, generate_eoi: false, export: false },
|
||||
berths: { view: true, edit: false, import: false, manage_waiting_list: false },
|
||||
documents: { view: true, create: false, send_for_signing: false, upload_signed: false, delete: false },
|
||||
expenses: { view: true, create: false, edit: false, delete: false, export: false, scan_receipt: false },
|
||||
invoices: { view: true, create: false, edit: false, delete: false, send: false, record_payment: false, export: false },
|
||||
files: { view: true, upload: false, delete: false, manage_folders: false },
|
||||
email: { view: true, send: false, configure_account: false },
|
||||
reminders: { view_own: true, view_all: false, create: false, edit_own: false, edit_all: false, assign_others: false },
|
||||
calendar: { connect: false, view_events: true },
|
||||
reports: { view_dashboard: true, view_analytics: false, export: false },
|
||||
document_templates: { view: true, generate: false, manage: false },
|
||||
admin: {
|
||||
manage_users: false,
|
||||
view_audit_log: false,
|
||||
manage_settings: false,
|
||||
manage_webhooks: false,
|
||||
manage_reports: false,
|
||||
manage_custom_fields: false,
|
||||
manage_forms: false,
|
||||
manage_tags: false,
|
||||
system_backup: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** Sales agent permissions — own clients/interests, no admin. */
|
||||
export function makeSalesAgentPermissions(): RolePermissions {
|
||||
return {
|
||||
clients: { view: true, create: true, edit: true, delete: false, merge: false, export: false },
|
||||
interests: { view: true, create: true, edit: true, delete: false, change_stage: true, generate_eoi: true, export: false },
|
||||
berths: { view: true, edit: false, import: false, manage_waiting_list: false },
|
||||
documents: { view: true, create: true, send_for_signing: true, upload_signed: true, delete: false },
|
||||
expenses: { view: true, create: true, edit: true, delete: false, export: false, scan_receipt: true },
|
||||
invoices: { view: true, create: false, edit: false, delete: false, send: false, record_payment: false, export: false },
|
||||
files: { view: true, upload: true, delete: false, manage_folders: false },
|
||||
email: { view: true, send: true, configure_account: false },
|
||||
reminders: { view_own: true, view_all: false, create: true, edit_own: true, edit_all: false, assign_others: false },
|
||||
calendar: { connect: true, view_events: true },
|
||||
reports: { view_dashboard: true, view_analytics: false, export: false },
|
||||
document_templates: { view: true, generate: true, manage: false },
|
||||
admin: {
|
||||
manage_users: false,
|
||||
view_audit_log: false,
|
||||
manage_settings: false,
|
||||
manage_webhooks: false,
|
||||
manage_reports: false,
|
||||
manage_custom_fields: false,
|
||||
manage_forms: false,
|
||||
manage_tags: false,
|
||||
system_backup: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** Sales manager — can do most things, limited admin. */
|
||||
export function makeSalesManagerPermissions(): RolePermissions {
|
||||
return {
|
||||
clients: { view: true, create: true, edit: true, delete: true, merge: true, export: true },
|
||||
interests: { view: true, create: true, edit: true, delete: true, change_stage: true, generate_eoi: true, export: true },
|
||||
berths: { view: true, edit: true, import: false, manage_waiting_list: true },
|
||||
documents: { view: true, create: true, send_for_signing: true, upload_signed: true, delete: true },
|
||||
expenses: { view: true, create: true, edit: true, delete: true, export: true, scan_receipt: true },
|
||||
invoices: { view: true, create: true, edit: true, delete: false, send: true, record_payment: true, export: true },
|
||||
files: { view: true, upload: true, delete: true, manage_folders: true },
|
||||
email: { view: true, send: true, configure_account: false },
|
||||
reminders: { view_own: true, view_all: true, create: true, edit_own: true, edit_all: true, assign_others: true },
|
||||
calendar: { connect: true, view_events: true },
|
||||
reports: { view_dashboard: true, view_analytics: true, export: true },
|
||||
document_templates: { view: true, generate: true, manage: false },
|
||||
admin: {
|
||||
manage_users: false,
|
||||
view_audit_log: true,
|
||||
manage_settings: false,
|
||||
manage_webhooks: false,
|
||||
manage_reports: true,
|
||||
manage_custom_fields: false,
|
||||
manage_forms: false,
|
||||
manage_tags: true,
|
||||
system_backup: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** Director — everything except system backup. */
|
||||
export function makeDirectorPermissions(): RolePermissions {
|
||||
return {
|
||||
...makeFullPermissions(),
|
||||
admin: {
|
||||
...makeFullPermissions().admin,
|
||||
system_backup: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// ─── Minimal valid CreateClientInput ─────────────────────────────────────────
|
||||
/** Returns a minimal valid CreateClientInput object for use in service calls. */
|
||||
export function makeCreateClientInput(overrides?: { fullName?: string; portId?: string }) {
|
||||
return {
|
||||
fullName: overrides?.fullName ?? 'Test Client',
|
||||
contacts: [{ channel: 'email' as const, value: 'test@example.com', isPrimary: true }],
|
||||
isProxy: false,
|
||||
tagIds: [] as string[],
|
||||
};
|
||||
}
|
||||
|
||||
/** Returns a minimal valid CreateInterestInput object. */
|
||||
export function makeCreateInterestInput(overrides?: {
|
||||
clientId?: string;
|
||||
pipelineStage?: 'open' | 'details_sent' | 'in_communication' | 'visited' | 'signed_eoi_nda' | 'deposit_10pct' | 'contract' | 'completed';
|
||||
}) {
|
||||
return {
|
||||
clientId: overrides?.clientId ?? crypto.randomUUID(),
|
||||
pipelineStage: overrides?.pipelineStage ?? ('open' as const),
|
||||
reminderEnabled: false,
|
||||
tagIds: [] as string[],
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user