Skip to content
Closed
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
192 changes: 192 additions & 0 deletions pkgs/cli/__tests__/commands/install/create-example-worker.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import fs from 'fs';
import path from 'path';
import os from 'os';
import { createExampleWorker } from '../../../src/commands/install/create-example-worker';
import { getVersion } from '../../../src/utils/get-version';

describe('createExampleWorker', () => {
let tempDir: string;
let supabasePath: string;
let workerDir: string;

beforeEach(() => {
// Create a temporary directory for testing
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pgflow-test-'));
supabasePath = path.join(tempDir, 'supabase');
workerDir = path.join(supabasePath, 'functions', 'greet-user-worker');
});

afterEach(() => {
// Clean up the temporary directory
fs.rmSync(tempDir, { recursive: true, force: true });
});

it('should create both files when none exist', async () => {
const result = await createExampleWorker({
supabasePath,
autoConfirm: true,
});

// Should return true because files were created
expect(result).toBe(true);

// Verify directory was created
expect(fs.existsSync(workerDir)).toBe(true);

// Verify all files exist
const indexPath = path.join(workerDir, 'index.ts');
const denoJsonPath = path.join(workerDir, 'deno.json');

expect(fs.existsSync(indexPath)).toBe(true);
expect(fs.existsSync(denoJsonPath)).toBe(true);
});

it('should create index.ts that imports GreetUser and starts EdgeWorker', async () => {
await createExampleWorker({
supabasePath,
autoConfirm: true,
});

const indexPath = path.join(workerDir, 'index.ts');
const indexContent = fs.readFileSync(indexPath, 'utf8');

// Should import EdgeWorker
expect(indexContent).toContain("import { EdgeWorker } from '@pgflow/edge-worker'");
// Should import GreetUser from flows directory
expect(indexContent).toContain("import { GreetUser } from '../../flows/greet-user.ts'");
// Should start EdgeWorker with GreetUser
expect(indexContent).toContain('EdgeWorker.start(GreetUser)');
});

it('should create deno.json with correct import mappings', async () => {
await createExampleWorker({
supabasePath,
autoConfirm: true,
});

const denoJsonPath = path.join(workerDir, 'deno.json');
const denoJsonContent = fs.readFileSync(denoJsonPath, 'utf8');
const denoJson = JSON.parse(denoJsonContent);

// Verify imports exist
expect(denoJson.imports).toBeDefined();
expect(denoJson.imports['@pgflow/core']).toBeDefined();
expect(denoJson.imports['@pgflow/dsl']).toBeDefined();
expect(denoJson.imports['@pgflow/edge-worker']).toBeDefined();
});

it('should inject package version instead of @latest in deno.json', async () => {
await createExampleWorker({
supabasePath,
autoConfirm: true,
});

const denoJsonPath = path.join(workerDir, 'deno.json');
const denoJsonContent = fs.readFileSync(denoJsonPath, 'utf8');
const denoJson = JSON.parse(denoJsonContent);

const version = getVersion();

// Verify version is not 'unknown'
expect(version).not.toBe('unknown');

// Verify that @latest is NOT used
expect(denoJsonContent).not.toContain('@latest');

// Verify that the actual version is used
expect(denoJson.imports['@pgflow/core']).toBe(`npm:@pgflow/core@${version}`);
expect(denoJson.imports['@pgflow/dsl']).toBe(`npm:@pgflow/dsl@${version}`);
expect(denoJson.imports['@pgflow/edge-worker']).toBe(`jsr:@pgflow/edge-worker@${version}`);
});

it('should not create files when they already exist', async () => {
// Pre-create the directory and files
fs.mkdirSync(workerDir, { recursive: true });

const indexPath = path.join(workerDir, 'index.ts');
const denoJsonPath = path.join(workerDir, 'deno.json');

fs.writeFileSync(indexPath, '// existing content');
fs.writeFileSync(denoJsonPath, '// existing content');

const result = await createExampleWorker({
supabasePath,
autoConfirm: true,
});

// Should return false because no changes were needed
expect(result).toBe(false);

// Verify files still exist with original content
expect(fs.readFileSync(indexPath, 'utf8')).toBe('// existing content');
expect(fs.readFileSync(denoJsonPath, 'utf8')).toBe('// existing content');
});

it('should create only missing files when some already exist', async () => {
// Pre-create the directory and one file
fs.mkdirSync(workerDir, { recursive: true });

const indexPath = path.join(workerDir, 'index.ts');
const denoJsonPath = path.join(workerDir, 'deno.json');

// Only create index.ts
fs.writeFileSync(indexPath, '// existing content');

const result = await createExampleWorker({
supabasePath,
autoConfirm: true,
});

// Should return true because deno.json was created
expect(result).toBe(true);

// Verify index.ts was not modified
expect(fs.readFileSync(indexPath, 'utf8')).toBe('// existing content');

// Verify deno.json was created
expect(fs.existsSync(denoJsonPath)).toBe(true);

const denoJsonContent = fs.readFileSync(denoJsonPath, 'utf8');
expect(denoJsonContent).toContain('"imports"');
});

it('should create parent directories if they do not exist', async () => {
// Don't create anything - let the function create it all
expect(fs.existsSync(supabasePath)).toBe(false);

const result = await createExampleWorker({
supabasePath,
autoConfirm: true,
});

expect(result).toBe(true);

// Verify all parent directories were created
expect(fs.existsSync(supabasePath)).toBe(true);
expect(fs.existsSync(path.join(supabasePath, 'functions'))).toBe(true);
expect(fs.existsSync(workerDir)).toBe(true);

// Verify files exist
expect(fs.existsSync(path.join(workerDir, 'index.ts'))).toBe(true);
expect(fs.existsSync(path.join(workerDir, 'deno.json'))).toBe(true);
});

it('should include subpath exports for Deno import mapping', async () => {
await createExampleWorker({
supabasePath,
autoConfirm: true,
});

const denoJsonPath = path.join(workerDir, 'deno.json');
const denoJsonContent = fs.readFileSync(denoJsonPath, 'utf8');
const denoJson = JSON.parse(denoJsonContent);

const version = getVersion();

// Verify subpath exports include versions (needed for proper Deno import mapping)
expect(denoJson.imports['@pgflow/core/']).toBe(`npm:@pgflow/core@${version}/`);
expect(denoJson.imports['@pgflow/dsl/']).toBe(`npm:@pgflow/dsl@${version}/`);
expect(denoJson.imports['@pgflow/edge-worker/']).toBe(`jsr:@pgflow/edge-worker@${version}/`);
});
});
66 changes: 42 additions & 24 deletions pkgs/cli/__tests__/commands/install/create-flows-directory.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ describe('createFlowsDirectory', () => {

// Verify all files exist
const indexPath = path.join(flowsDir, 'index.ts');
const exampleFlowPath = path.join(flowsDir, 'example-flow.ts');
const greetUserPath = path.join(flowsDir, 'greet-user.ts');

expect(fs.existsSync(indexPath)).toBe(true);
expect(fs.existsSync(exampleFlowPath)).toBe(true);
expect(fs.existsSync(greetUserPath)).toBe(true);
});

it('should create index.ts with barrel export pattern', async () => {
Expand All @@ -50,42 +50,60 @@ describe('createFlowsDirectory', () => {
const indexPath = path.join(flowsDir, 'index.ts');
const indexContent = fs.readFileSync(indexPath, 'utf8');

// Should have export for ExampleFlow
expect(indexContent).toContain("export { ExampleFlow } from './example-flow.ts';");
// Should have export for GreetUser
expect(indexContent).toContain("export { GreetUser } from './greet-user.ts';");
// Should have documenting comment
expect(indexContent).toContain('Re-export all flows');
});

it('should create example-flow.ts with named export', async () => {
it('should create greet-user.ts with named export', async () => {
await createFlowsDirectory({
supabasePath,
autoConfirm: true,
});

const exampleFlowPath = path.join(flowsDir, 'example-flow.ts');
const exampleFlowContent = fs.readFileSync(exampleFlowPath, 'utf8');
const greetUserPath = path.join(flowsDir, 'greet-user.ts');
const greetUserContent = fs.readFileSync(greetUserPath, 'utf8');

// Should use named export (not default)
expect(exampleFlowContent).toContain('export const ExampleFlow');
expect(greetUserContent).toContain('export const GreetUser');
// Should import Flow from @pgflow/dsl
expect(exampleFlowContent).toContain("import { Flow } from '@pgflow/dsl'");
expect(greetUserContent).toContain("import { Flow } from '@pgflow/dsl'");
// Should have correct slug
expect(exampleFlowContent).toContain("slug: 'exampleFlow'");
// Should have input type
expect(exampleFlowContent).toContain('type Input');
// Should have at least one step
expect(exampleFlowContent).toContain('.step(');
expect(greetUserContent).toContain("slug: 'greetUser'");
// Should have input type with firstName and lastName
expect(greetUserContent).toContain('type Input');
expect(greetUserContent).toContain('firstName');
expect(greetUserContent).toContain('lastName');
});

it('should create greet-user.ts with two steps showing dependsOn', async () => {
await createFlowsDirectory({
supabasePath,
autoConfirm: true,
});

const greetUserPath = path.join(flowsDir, 'greet-user.ts');
const greetUserContent = fs.readFileSync(greetUserPath, 'utf8');

// Should have two steps
expect(greetUserContent).toContain("slug: 'fullName'");
expect(greetUserContent).toContain("slug: 'greeting'");
// Second step should depend on first
expect(greetUserContent).toContain("dependsOn: ['fullName']");
// Second step should access result from first step
expect(greetUserContent).toContain('input.fullName');
});

it('should not create files when they already exist', async () => {
// Pre-create the directory and files
fs.mkdirSync(flowsDir, { recursive: true });

const indexPath = path.join(flowsDir, 'index.ts');
const exampleFlowPath = path.join(flowsDir, 'example-flow.ts');
const greetUserPath = path.join(flowsDir, 'greet-user.ts');

fs.writeFileSync(indexPath, '// existing content');
fs.writeFileSync(exampleFlowPath, '// existing content');
fs.writeFileSync(greetUserPath, '// existing content');

const result = await createFlowsDirectory({
supabasePath,
Expand All @@ -97,15 +115,15 @@ describe('createFlowsDirectory', () => {

// Verify files still exist with original content
expect(fs.readFileSync(indexPath, 'utf8')).toBe('// existing content');
expect(fs.readFileSync(exampleFlowPath, 'utf8')).toBe('// existing content');
expect(fs.readFileSync(greetUserPath, 'utf8')).toBe('// existing content');
});

it('should create only missing files when some already exist', async () => {
// Pre-create the directory and one file
fs.mkdirSync(flowsDir, { recursive: true });

const indexPath = path.join(flowsDir, 'index.ts');
const exampleFlowPath = path.join(flowsDir, 'example-flow.ts');
const greetUserPath = path.join(flowsDir, 'greet-user.ts');

// Only create index.ts
fs.writeFileSync(indexPath, '// existing content');
Expand All @@ -115,17 +133,17 @@ describe('createFlowsDirectory', () => {
autoConfirm: true,
});

// Should return true because example-flow.ts was created
// Should return true because greet-user.ts was created
expect(result).toBe(true);

// Verify index.ts was not modified
expect(fs.readFileSync(indexPath, 'utf8')).toBe('// existing content');

// Verify example-flow.ts was created
expect(fs.existsSync(exampleFlowPath)).toBe(true);
// Verify greet-user.ts was created
expect(fs.existsSync(greetUserPath)).toBe(true);

const exampleContent = fs.readFileSync(exampleFlowPath, 'utf8');
expect(exampleContent).toContain('export const ExampleFlow');
const greetUserContent = fs.readFileSync(greetUserPath, 'utf8');
expect(greetUserContent).toContain('export const GreetUser');
});

it('should create parent directories if they do not exist', async () => {
Expand All @@ -145,6 +163,6 @@ describe('createFlowsDirectory', () => {

// Verify files exist
expect(fs.existsSync(path.join(flowsDir, 'index.ts'))).toBe(true);
expect(fs.existsSync(path.join(flowsDir, 'example-flow.ts'))).toBe(true);
expect(fs.existsSync(path.join(flowsDir, 'greet-user.ts'))).toBe(true);
});
});
48 changes: 0 additions & 48 deletions pkgs/cli/examples/async-function-hang.ts

This file was deleted.

Loading