chore: kanban template

This commit is contained in:
e2e
2026-06-21 21:15:34 +02:00
commit d0b6864c4c
209 changed files with 215578 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
# Environment variables required by createConfig() during tests.
# Add any env vars your config.ts reads at module load time.
# This file is loaded via --env-file and is NOT committed to version control.
NODE_ENV=test

View File

@@ -0,0 +1,200 @@
{
"generatedAt": "2026-05-28T21:20:37.531Z",
"summary": {
"total": 17,
"covered": 2,
"partial": 1,
"uncovered": 14,
"unknown": 0,
"overallRatio": 0.118
},
"functions": [
{
"name": "createCardTool",
"sourceFile": "src/wirings/kanban.mcp.ts",
"exposed": true,
"description": "Create a new kanban card on the board.",
"coveredLines": 0,
"totalLines": 11,
"missedLines": [28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38],
"ratio": 0,
"status": "uncovered"
},
{
"name": "enrichCard",
"sourceFile": "src/functions/enrich-card.function.ts",
"exposed": true,
"description": null,
"coveredLines": 0,
"totalLines": 9,
"missedLines": [20, 21, 22, 23, 24, 25, 26, 27, 28],
"ratio": 0,
"status": "uncovered"
},
{
"name": "getCardFileUploadUrl",
"sourceFile": "src/functions/get-card-file-upload-url.function.ts",
"exposed": true,
"description": "Return a presigned upload URL for attaching a file to a card.",
"coveredLines": 0,
"totalLines": 16,
"missedLines": [24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39],
"ratio": 0,
"status": "uncovered"
},
{
"name": "listCardsTool",
"sourceFile": "src/wirings/kanban.mcp.ts",
"exposed": true,
"description": "List all kanban cards on the board.",
"coveredLines": 0,
"totalLines": 6,
"missedLines": [9, 10, 11, 12, 13, 14],
"ratio": 0,
"status": "uncovered"
},
{
"name": "notifyCardCreated",
"sourceFile": "src/functions/notify-card-created.function.ts",
"exposed": true,
"description": null,
"coveredLines": 0,
"totalLines": 1,
"missedLines": [14],
"ratio": 0,
"status": "uncovered"
},
{
"name": "onCardEventTrigger",
"sourceFile": "src/functions/on-card-event-trigger.function.ts",
"exposed": true,
"description": null,
"coveredLines": 0,
"totalLines": 11,
"missedLines": [22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32],
"ratio": 0,
"status": "uncovered"
},
{
"name": "onCardsClearSession",
"sourceFile": "src/functions/cards-channel.function.ts",
"exposed": true,
"description": null,
"coveredLines": 0,
"totalLines": 2,
"missedLines": [76, 77],
"ratio": 0,
"status": "uncovered"
},
{
"name": "onCardsConnect",
"sourceFile": "src/functions/cards-channel.function.ts",
"exposed": true,
"description": null,
"coveredLines": 0,
"totalLines": 3,
"missedLines": [16, 17, 18],
"ratio": 0,
"status": "uncovered"
},
{
"name": "onCardsDisconnect",
"sourceFile": "src/functions/cards-channel.function.ts",
"exposed": true,
"description": null,
"coveredLines": 0,
"totalLines": 1,
"missedLines": [23],
"ratio": 0,
"status": "uncovered"
},
{
"name": "onCardsEcho",
"sourceFile": "src/functions/cards-channel.function.ts",
"exposed": true,
"description": null,
"coveredLines": 0,
"totalLines": 1,
"missedLines": [35],
"ratio": 0,
"status": "uncovered"
},
{
"name": "onCardsGetSession",
"sourceFile": "src/functions/cards-channel.function.ts",
"exposed": true,
"description": null,
"coveredLines": 0,
"totalLines": 2,
"missedLines": [68, 69],
"ratio": 0,
"status": "uncovered"
},
{
"name": "onCardsSetSession",
"sourceFile": "src/functions/cards-channel.function.ts",
"exposed": true,
"description": null,
"coveredLines": 0,
"totalLines": 2,
"missedLines": [60, 61],
"ratio": 0,
"status": "uncovered"
},
{
"name": "processCardEvent",
"sourceFile": "src/functions/process-card-event.function.ts",
"exposed": true,
"description": null,
"coveredLines": 0,
"totalLines": 1,
"missedLines": [19],
"ratio": 0,
"status": "uncovered"
},
{
"name": "signCardFile",
"sourceFile": "src/functions/sign-card-file.function.ts",
"exposed": true,
"description": "Return a time-limited signed download URL for a card attachment.",
"coveredLines": 0,
"totalLines": 14,
"missedLines": [20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33],
"ratio": 0,
"status": "uncovered"
},
{
"name": "archiveStaleCards",
"sourceFile": "src/functions/archive-stale-cards.function.ts",
"exposed": true,
"description": null,
"coveredLines": 1,
"totalLines": 3,
"missedLines": [5, 6],
"ratio": 0.333,
"status": "partial"
},
{
"name": "createCard",
"sourceFile": "src/functions/create-card.function.ts",
"exposed": true,
"description": "Create a kanban card.",
"coveredLines": 19,
"totalLines": 19,
"missedLines": [],
"ratio": 1,
"status": "covered"
},
{
"name": "listCards",
"sourceFile": "src/functions/list-cards.function.ts",
"exposed": true,
"description": "List all kanban cards, optionally filtered by status.",
"coveredLines": 6,
"totalLines": 6,
"missedLines": [],
"ratio": 1,
"status": "covered"
}
]
}

View File

@@ -0,0 +1,27 @@
{
"name": "tests",
"version": "0.0.1",
"private": true,
"description": "Function test harness — Cucumber over in-process Pikku RPC with stubbed services.",
"type": "module",
"scripts": {
"test": "node --env-file=.env.test node_modules/.bin/cucumber-js --config tests/cucumber.mjs --tags 'not @skip'",
"test:tag": "node --env-file=.env.test node_modules/.bin/cucumber-js --config tests/cucumber.mjs --tags",
"coverage": "npx pikku tests coverage",
"tsc": "tsc --noEmit"
},
"dependencies": {
"@pikku/ai-vercel": "^0.12.6",
"@pikku/cucumber": "^0.12.4",
"@pikku/kysely-node-sqlite": "^0.12.2",
"@pikku/kysely-sqlite": "^0.12.6",
"@pikku/schema-cfworker": "^0.12.2"
},
"devDependencies": {
"@cucumber/cucumber": "^11.0.0",
"@types/node": "^22",
"c8": "^10.0.0",
"tsx": "^4.21.0",
"typescript": "~5.8.0"
}
}

View File

@@ -0,0 +1,7 @@
export default {
requireModule: ['tsx'],
require: ['tests/support/**/*.ts', 'tests/steps/**/*.ts'],
paths: ['tests/features/**/*.feature'],
format: ['progress', 'html:tests/reports/cucumber-report.html'],
forceExit: true,
}

View File

@@ -0,0 +1,20 @@
Feature: Kanban card management
Scenario: user can list all cards
When "anon" calls "listCards"
Then the call succeeds
Scenario: user can create a card
When "anon" calls "createCard" with:
| title | Test card |
| status | todo |
Then the call succeeds
And the result has "cardId"
Scenario: created card appears in list
When "anon" calls "createCard" with:
| title | E2E card |
| status | in_progress |
Then the call succeeds
When "anon" calls "listCards"
Then the call succeeds

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,16 @@
import '../../../.pikku/pikku-bootstrap.gen.js'
import {
Before,
After,
BeforeAll,
AfterAll,
setDefaultTimeout,
Given,
When,
Then,
} from '@cucumber/cucumber'
import { registerHooks, registerCommonSteps } from '@pikku/cucumber'
import { db } from './services.js'
registerHooks({ Before, After, BeforeAll, AfterAll, setDefaultTimeout }, db)
registerCommonSteps({ Given, When, Then })

View File

@@ -0,0 +1,51 @@
import { createNodeSqliteKysely } from '@pikku/kysely-node-sqlite'
import { createDbUtils, type StubTracker } from '@pikku/cucumber'
import { createConfig } from '../../../src/config.js'
import { createSingletonServices } from '../../../src/services.js'
import type { DB } from '../../../db/schema.js'
import { dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
const __dirname = dirname(fileURLToPath(import.meta.url))
const repoRoot = (p: string) => resolve(__dirname, '../../../../..', p)
export const db = createDbUtils({
migrationsDir: repoRoot('db/migrations'),
seedFile: repoRoot('db/seed.sql'),
})
type StubKysely = ReturnType<typeof createNodeSqliteKysely<DB>>
const REAL_SERVICES = (kysely: StubKysely) => ({ kysely })
export async function createStubServices(dbFile: string, tracker: StubTracker) {
const kysely = createNodeSqliteKysely<DB>({
filename: dbFile,
camelCase: true,
})
const real = REAL_SERVICES(kysely)
const injected = new Proxy(real as Record<string, unknown>, {
get(target, prop: string) {
if (prop in target) return target[prop]
return tracker.stub(prop)
},
})
const config = await createConfig()
const base = await createSingletonServices(
{ ...config, sqliteDb: dbFile } as never,
injected as never,
)
// Spreading a Proxy loses non-enumerable stubs; wrap the result so any
// service not explicitly provided falls through to the tracker stub.
const services = new Proxy(base as Record<string, unknown>, {
get(target, prop: string) {
if (prop in target && target[prop] !== undefined) return target[prop]
return tracker.stub(prop)
},
})
return { services: services as typeof base, kysely }
}

View File

@@ -0,0 +1,5 @@
import { World, setWorldConstructor } from '@cucumber/cucumber'
import { createFunctionWorld } from '@pikku/cucumber'
import { createStubServices } from './services.js'
createFunctionWorld(World, setWorldConstructor, createStubServices)

View File

@@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"types": ["node"],
"noEmit": true,
"skipLibCheck": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"strict": true
},
"include": ["tests/**/*.ts"]
}