Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/api/src/auth/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {
const router = Router();

const refreshTokenSchema = z.object({
refreshToken: z.string().min(1, 'refreshToken is required'),
refreshToken: z.string().uuid('refreshToken must be a valid UUID'),
});

router.post(
Expand Down
74 changes: 74 additions & 0 deletions packages/api/src/collections/__tests__/service-crud-unit.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';

const {
readdirMock,
getCurrentRevisionMock,
} = vi.hoisted(() => ({
readdirMock: vi.fn(),
getCurrentRevisionMock: vi.fn(),
}));

vi.mock('fs/promises', () => ({
default: {
readdir: readdirMock,
},
readdir: readdirMock,
}));

vi.mock('../git/service', () => ({
GitService: vi.fn().mockImplementation(() => ({
getCurrentRevision: getCurrentRevisionMock,
getWorkspaceDir: vi.fn().mockReturnValue('/tmp/workspace'),
})),
}));

vi.mock('../git/runtime', () => ({
getProjectGit: vi.fn().mockResolvedValue({}),
ensureProjectCloned: vi.fn().mockResolvedValue(undefined),
withPreparedGit: vi.fn().mockImplementation(async (_projectId, _branch, fn) => fn()),
}));

import { CollectionService } from '../service';

describe('CollectionService CRUD unit tests', () => {
let service: CollectionService;

beforeEach(() => {
vi.clearAllMocks();
service = new CollectionService({ projectId: 'test-project', branch: 'main' });
(service as any).workspacePath = '/tmp/workspace';
(service as any).gitService = {
getCurrentRevision: getCurrentRevisionMock,
getWorkspaceDir: vi.fn().mockReturnValue('/tmp/workspace'),
};
});

it('listCollections returns empty array when no collections exist', async () => {
readdirMock.mockRejectedValue(new Error('ENOENT'));
const result = await service.listCollections();
expect(result).toEqual([]);
});

it('getCollectionConfig returns null for non-existent collection', async () => {
readdirMock.mockRejectedValue(new Error('ENOENT'));
const result = await service.getCollectionConfig('nonexistent');
expect(result).toBeNull();
});

it('findOne returns null when collection does not exist', async () => {
readdirMock.mockRejectedValue(new Error('ENOENT'));
const result = await service.findOne('nonexistent', 'entry-1');
expect(result).toBeNull();
});

it('findMany throws when collection does not exist', async () => {
readdirMock.mockRejectedValue(new Error('ENOENT'));
await expect(service.findMany('nonexistent')).rejects.toThrow("Collection 'nonexistent' not found");
});

it('getCurrentRevision returns current git revision', async () => {
getCurrentRevisionMock.mockResolvedValue('rev123');
const result = await service.getCurrentRevision();
expect(result).toBe('rev123');
});
});
30 changes: 30 additions & 0 deletions packages/cli/src/commands/__tests__/auth.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { describe, expect, it, vi, beforeEach } from 'vitest';
import { Command } from 'commander';
import { authCommand } from '../auth';

vi.mock('../lib/config.js', () => ({
loadConfig: vi.fn(),
saveConfig: vi.fn(),
clearConfig: vi.fn(),
}));

describe('authCommand', () => {
let program: Command;

beforeEach(() => {
program = new Command();
authCommand(program);
});

it('registers login command', () => {
const loginCmd = program.commands.find((c) => c.name() === 'login');
expect(loginCmd).toBeDefined();
expect(loginCmd?.description()).toBe('Authenticate with OriCMS instance');
});

it('registers logout command', () => {
const logoutCmd = program.commands.find((c) => c.name() === 'logout');
expect(logoutCmd).toBeDefined();
expect(logoutCmd?.description()).toBe('Logout from OriCMS');
});
});
25 changes: 25 additions & 0 deletions packages/cli/src/commands/__tests__/cdn.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { describe, expect, it, vi, beforeEach } from 'vitest';
import { Command } from 'commander';
import { cdnCommand } from '../cdn';

describe('cdnCommand', () => {
let program: Command;

beforeEach(() => {
program = new Command();
cdnCommand(program);
});

it('registers cdn command', () => {
const cdnCmd = program.commands.find((c) => c.name() === 'cdn');
expect(cdnCmd).toBeDefined();
expect(cdnCmd?.description()).toBe('CDN export and management');
});

it('registers cdn export subcommand', () => {
const cdnCmd = program.commands.find((c) => c.name() === 'cdn');
const exportCmd = cdnCmd?.commands.find((c) => c.name() === 'export');
expect(exportCmd).toBeDefined();
expect(exportCmd?.description()).toBe('Export build output to CDN');
});
});
18 changes: 18 additions & 0 deletions packages/cli/src/commands/__tests__/deploy.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { describe, expect, it, vi, beforeEach } from 'vitest';
import { Command } from 'commander';
import { deployCommand } from '../deploy';

describe('deployCommand', () => {
let program: Command;

beforeEach(() => {
program = new Command();
deployCommand(program);
});

it('registers deploy command', () => {
const deployCmd = program.commands.find((c) => c.name() === 'deploy');
expect(deployCmd).toBeDefined();
expect(deployCmd?.description()).toBe('Deploy project to configured CDN');
});
});
18 changes: 18 additions & 0 deletions packages/cli/src/commands/__tests__/init.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { describe, expect, it, vi, beforeEach } from 'vitest';
import { Command } from 'commander';
import { initCommand } from '../init';

describe('initCommand', () => {
let program: Command;

beforeEach(() => {
program = new Command();
initCommand(program);
});

it('registers init command', () => {
const initCmd = program.commands.find((c) => c.name() === 'init');
expect(initCmd).toBeDefined();
expect(initCmd?.description()).toBe('Initialize a new OriCMS project');
});
});
32 changes: 32 additions & 0 deletions packages/cli/src/commands/__tests__/project.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { describe, expect, it, vi, beforeEach } from 'vitest';
import { Command } from 'commander';
import { projectCommand } from '../project';

describe('projectCommand', () => {
let program: Command;

beforeEach(() => {
program = new Command();
projectCommand(program);
});

it('registers project command', () => {
const projectCmd = program.commands.find((c) => c.name() === 'project');
expect(projectCmd).toBeDefined();
expect(projectCmd?.description()).toBe('Manage OriCMS projects');
});

it('registers project list subcommand', () => {
const projectCmd = program.commands.find((c) => c.name() === 'project');
const listCmd = projectCmd?.commands.find((c) => c.name() === 'list');
expect(listCmd).toBeDefined();
expect(listCmd?.description()).toBe('List your projects');
});

it('registers project create subcommand', () => {
const projectCmd = program.commands.find((c) => c.name() === 'project');
const createCmd = projectCmd?.commands.find((c) => c.name() === 'create');
expect(createCmd).toBeDefined();
expect(createCmd?.description()).toBe('Create a new project');
});
});
18 changes: 18 additions & 0 deletions packages/cli/src/commands/__tests__/start.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { describe, expect, it, vi, beforeEach } from 'vitest';
import { Command } from 'commander';
import { startCommand } from '../start';

describe('startCommand', () => {
let program: Command;

beforeEach(() => {
program = new Command();
startCommand(program);
});

it('registers start command', () => {
const startCmd = program.commands.find((c) => c.name() === 'start');
expect(startCmd).toBeDefined();
expect(startCmd?.description()).toBe('Start OriCMS services (PostgreSQL, API, Web) with Docker Compose');
});
});
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useMemo } from 'react';
import { createContext, useContext, type ReactNode } from 'react';
import { useCollectionManager } from '../../hooks/useCollectionManager';

Expand All @@ -6,7 +7,8 @@ const CollectionManagerContext = createContext<CollectionManagerValue | null>(nu

export function CollectionManagerProvider({ children, ...options }: { children: ReactNode } & Parameters<typeof useCollectionManager>[0]) {
const value = useCollectionManager(options);
return <CollectionManagerContext.Provider value={value}>{children}</CollectionManagerContext.Provider>;
const memoizedValue = useMemo(() => value, Object.values(value));
return <CollectionManagerContext.Provider value={memoizedValue}>{children}</CollectionManagerContext.Provider>;
}

export function useCollectionManagerContext() {
Expand Down
4 changes: 3 additions & 1 deletion packages/web/src/contexts/workspace/EditorContext.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useMemo } from 'react';
import { createContext, useContext, type ReactNode } from 'react';
import { useEntryEditor } from '../../hooks/useEntryEditor';

Expand All @@ -6,7 +7,8 @@ const EditorContext = createContext<EditorValue | null>(null);

export function EditorProvider({ children, ...options }: { children: ReactNode } & Parameters<typeof useEntryEditor>[0]) {
const value = useEntryEditor(options);
return <EditorContext.Provider value={value}>{children}</EditorContext.Provider>;
const memoizedValue = useMemo(() => value, Object.values(value));
return <EditorContext.Provider value={memoizedValue}>{children}</EditorContext.Provider>;
}

export function useEditorContext() {
Expand Down
4 changes: 3 additions & 1 deletion packages/web/src/contexts/workspace/SchemaEditorContext.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useMemo } from 'react';
import { createContext, useContext, type ReactNode } from 'react';
import { useSchemaEditor } from '../../hooks/useSchemaEditor';

Expand All @@ -6,7 +7,8 @@ const SchemaEditorContext = createContext<SchemaEditorValue | null>(null);

export function SchemaEditorProvider({ children, ...options }: { children: ReactNode } & Parameters<typeof useSchemaEditor>[0]) {
const value = useSchemaEditor(options);
return <SchemaEditorContext.Provider value={value}>{children}</SchemaEditorContext.Provider>;
const memoizedValue = useMemo(() => value, Object.values(value));
return <SchemaEditorContext.Provider value={memoizedValue}>{children}</SchemaEditorContext.Provider>;
}

export function useSchemaEditorContext() {
Expand Down
Loading