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
25 changes: 25 additions & 0 deletions .cascade/ensure-services.sh
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,29 @@ else
fi
fi

# Verify test database exists (needed for integration tests)
if pg_isready -q 2>/dev/null; then
# OS-aware psql command (macOS uses peer auth, Linux uses -U postgres)
case "$(uname -s)" in
Linux*) PSQL_CMD="psql -U postgres" ;;
*) PSQL_CMD="psql" ;;
esac

if $PSQL_CMD -lqt 2>/dev/null | cut -d \| -f 1 | grep -qw cascade_test; then
echo "Test database (cascade_test): exists"
else
echo "Test database (cascade_test): missing - creating..."
if [ "$(uname -s)" = "Linux" ]; then
$PSQL_CMD -c "CREATE DATABASE cascade_test;" 2>/dev/null || true
else
createdb cascade_test 2>/dev/null || true
fi
if $PSQL_CMD -lqt 2>/dev/null | cut -d \| -f 1 | grep -qw cascade_test; then
echo "Test database (cascade_test): created"
else
echo "Test database (cascade_test): FAILED TO CREATE (integration tests will not work)"
fi
fi
fi

echo "=== All services running ==="
1 change: 1 addition & 0 deletions .cascade/env
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
CI=true
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/cascade
DATABASE_SSL=false
TEST_DATABASE_URL=postgresql://postgres:postgres@localhost:5432/cascade_test
32 changes: 28 additions & 4 deletions .cascade/setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ if pg_isready -q 2>/dev/null; then
PSQL_CMD="sudo -u postgres psql"
fi

# Create cascade database
# Create cascade database (development)
if ! $PSQL_CMD -lqt 2>/dev/null | cut -d \| -f 1 | grep -qw cascade; then
log_info "Creating cascade database..."
if [ "$OS" = "linux" ]; then
Expand All @@ -236,6 +236,18 @@ if pg_isready -q 2>/dev/null; then
log_info "Database cascade already exists"
fi

# Create cascade_test database (integration tests)
if ! $PSQL_CMD -lqt 2>/dev/null | cut -d \| -f 1 | grep -qw cascade_test; then
log_info "Creating cascade_test database..."
if [ "$OS" = "linux" ]; then
$PSQL_CMD -c "CREATE DATABASE cascade_test;" 2>/dev/null || true
else
createdb cascade_test 2>/dev/null || true
fi
else
log_info "Database cascade_test already exists"
fi

# On Linux, ensure postgres user has a known password for app connections
if [ "$OS" = "linux" ]; then
$PSQL_CMD -c "ALTER USER postgres WITH PASSWORD 'postgres';" 2>/dev/null || true
Expand Down Expand Up @@ -266,9 +278,21 @@ echo ""
echo "--- Database Migrations ---"

if pg_isready -q 2>/dev/null; then
log_info "Running migrations..."
DATABASE_SSL=false npm run db:migrate 2>&1 || \
log_warn "Migration failed - may need manual intervention"
if [ "$OS" = "linux" ]; then
DEV_DB_URL="postgresql://postgres:postgres@localhost:5432/cascade"
TEST_DB_URL="postgresql://postgres:postgres@localhost:5432/cascade_test"
else
DEV_DB_URL="postgresql://localhost:5432/cascade"
TEST_DB_URL="postgresql://localhost:5432/cascade_test"
fi

log_info "Running migrations on cascade (dev)..."
DATABASE_URL="$DEV_DB_URL" DATABASE_SSL=false npm run db:migrate 2>&1 || \
log_warn "Migration failed on cascade - may need manual intervention"

log_info "Running migrations on cascade_test..."
DATABASE_URL="$TEST_DB_URL" DATABASE_SSL=false npm run db:migrate 2>&1 || \
log_warn "Migration failed on cascade_test - may need manual intervention"
else
log_warn "PostgreSQL not ready, skipping migrations"
fi
Expand Down
38 changes: 38 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,44 @@ jobs:
- name: Validate PR commits
run: npx commitlint --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }} --verbose

integration-tests:
runs-on: ubuntu-latest

services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: cascade_test
POSTGRES_PASSWORD: cascade_test
POSTGRES_DB: cascade_test
ports:
- 5433:5432
options: >-
--health-cmd "pg_isready -U cascade_test -d cascade_test"
--health-interval 2s
--health-timeout 5s
--health-retries 10

steps:
- uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "22"
cache: "npm"

- name: Install dependencies
run: npm ci

- name: Build backend
run: npm run build

- name: Run integration tests
run: npm run test:integration
env:
TEST_DATABASE_URL: postgresql://cascade_test:cascade_test@localhost:5433/cascade_test

docker-build-check:
name: Validate Docker builds
runs-on: ubuntu-latest
Expand Down
17 changes: 17 additions & 0 deletions docker-compose.test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
services:
postgres-test:
container_name: cascade-postgres-test
image: postgres:16-alpine
ports:
- "5433:5432"
environment:
POSTGRES_USER: cascade_test
POSTGRES_PASSWORD: cascade_test
POSTGRES_DB: cascade_test
tmpfs:
- /var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U cascade_test -d cascade_test"]
interval: 2s
timeout: 5s
retries: 10
11 changes: 8 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,14 @@
"build": "tsc",
"build:web": "cd web && npm run build",
"start": "node dist/index.js",
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage",
"test": "vitest run --project unit",
"test:unit": "vitest run --project unit",
"test:integration": "vitest run --project integration",
"test:all": "vitest run",
"test:watch": "vitest --project unit",
"test:coverage": "vitest run --project unit --coverage",
"test:db:up": "docker compose -f docker-compose.test.yml up -d --wait",
"test:db:down": "docker compose -f docker-compose.test.yml down -v",
"lint": "biome check .",
"lint:fix": "biome check --write .",
"typecheck": "tsc --noEmit",
Expand Down
58 changes: 36 additions & 22 deletions src/backends/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,39 @@ async function buildBackendInput(
};
}

/**
* Build progress-monitor config from pipeline inputs.
*/
function buildProgressMonitorConfig(
input: AgentInput & { config: CascadeConfig },
agentType: string,
logWriter: LogWriter,
repoDir: string | null,
isGitHubAck: boolean,
) {
const { cardId } = input;
return {
logWriter,
agentType,
taskDescription: cardId ? `Work item ${cardId}` : 'Unknown task',
progressModel: input.config.defaults.progressModel,
intervalMinutes: input.config.defaults.progressIntervalMinutes,
customModels: CUSTOM_MODELS as ModelSpec[],
repoDir: repoDir ?? undefined,
trello: cardId ? { cardId } : undefined,
preSeededCommentId: isGitHubAck ? undefined : (input.ackCommentId as string | undefined),
...(input.prNumber && input.repoFullName
? {
github: {
owner: input.repoFullName.split('/')[0],
repo: input.repoFullName.split('/')[1],
headerMessage: input.ackMessage ?? '',
},
}
: {}),
};
}

export async function executeWithBackend(
backend: AgentBackend,
agentType: string,
Expand Down Expand Up @@ -207,28 +240,9 @@ export async function executeWithBackend(
recordInitialComment(input.ackCommentId as number);
}

const monitor = createProgressMonitor({
logWriter,
agentType,
taskDescription: cardId ? `Work item ${cardId}` : 'Unknown task',
progressModel: input.config.defaults.progressModel,
intervalMinutes: input.config.defaults.progressIntervalMinutes,
customModels: CUSTOM_MODELS as ModelSpec[],
repoDir: repoDir ?? undefined,
trello: cardId ? { cardId } : undefined,
// Only use preSeededCommentId for PM (Trello/JIRA) ack comments, not GitHub
preSeededCommentId: isGitHubAck ? undefined : (input.ackCommentId as string | undefined),
// Pass GitHub config so progress monitor can update the PR comment
...(input.prNumber && input.repoFullName
? {
github: {
owner: input.repoFullName.split('/')[0],
repo: input.repoFullName.split('/')[1],
headerMessage: input.ackMessage ?? '',
},
}
: {}),
});
const monitor = createProgressMonitor(
buildProgressMonitorConfig(input, agentType, logWriter, repoDir, isGitHubAck),
);

const backendInput: AgentBackendInput = {
...partialInput,
Expand Down
104 changes: 104 additions & 0 deletions tests/helpers/factories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import type { TRPCContext, TRPCUser } from '../../src/api/trpc.js';
import type { ProjectConfig, TriggerContext } from '../../src/types/index.js';

// ---------------------------------------------------------------------------
// Project factories
// ---------------------------------------------------------------------------

/**
* Creates a mock Trello project config. Sensible defaults for trigger tests;
* pass overrides (shallow-merged) for test-specific customisation.
*/
export function createMockProject(overrides?: Partial<ProjectConfig>): ProjectConfig {
return {
id: 'test',
orgId: 'org-1',
name: 'Test',
repo: 'owner/repo',
baseBranch: 'main',
branchPrefix: 'feature/',
pm: { type: 'trello' },
trello: {
boardId: 'board123',
lists: {
splitting: 'splitting-list-id',
planning: 'planning-list-id',
todo: 'todo-list-id',
},
labels: {},
},
...overrides,
} as ProjectConfig;
}

/**
* Creates a mock JIRA project config.
*/
export function createMockJiraProject(overrides?: Partial<ProjectConfig>): ProjectConfig {
return {
id: 'jira-project',
orgId: 'org-1',
name: 'JIRA Project',
repo: 'owner/jira-repo',
baseBranch: 'main',
branchPrefix: 'feature/',
pm: { type: 'jira' },
jira: {
projectKey: 'PROJ',
baseUrl: 'https://test.atlassian.net',
statuses: { splitting: 'Briefing' },
labels: {
processing: 'my-processing',
processed: 'my-processed',
error: 'my-error',
readyToProcess: 'my-ready',
},
},
...overrides,
} as ProjectConfig;
}

// ---------------------------------------------------------------------------
// tRPC factories
// ---------------------------------------------------------------------------

/**
* Creates a mock tRPC user. Defaults to an admin user.
*/
export function createMockUser(overrides?: Partial<TRPCUser>): TRPCUser {
return {
id: 'user-1',
orgId: 'org-1',
email: 'test@example.com',
name: 'Test User',
role: 'admin',
...overrides,
};
}

/**
* Creates a mock tRPC context with an authenticated user.
*/
export function createMockContext(userOverrides?: Partial<TRPCUser>): TRPCContext {
const user = createMockUser(userOverrides);
return {
user,
effectiveOrgId: user.orgId,
};
}

// ---------------------------------------------------------------------------
// Trigger context factory
// ---------------------------------------------------------------------------

/**
* Creates a mock trigger context for trigger handler tests.
*/
export function createTriggerContext(overrides?: Partial<TriggerContext>): TriggerContext {
return {
project: createMockProject(),
source: 'trello',
payload: {},
...overrides,
} as TriggerContext;
}
Loading
Loading