chore: kanban template
This commit is contained in:
4
packages/functions/tests/.env.test
Normal file
4
packages/functions/tests/.env.test
Normal 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
|
||||
200
packages/functions/tests/coverage/function-coverage.json
Normal file
200
packages/functions/tests/coverage/function-coverage.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
27
packages/functions/tests/package.json
Normal file
27
packages/functions/tests/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
7
packages/functions/tests/tests/cucumber.mjs
Normal file
7
packages/functions/tests/tests/cucumber.mjs
Normal 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,
|
||||
}
|
||||
0
packages/functions/tests/tests/features/.gitkeep
Normal file
0
packages/functions/tests/tests/features/.gitkeep
Normal file
20
packages/functions/tests/tests/features/cards.feature
Normal file
20
packages/functions/tests/tests/features/cards.feature
Normal 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
|
||||
44355
packages/functions/tests/tests/reports/cucumber-report.html
Normal file
44355
packages/functions/tests/tests/reports/cucumber-report.html
Normal file
File diff suppressed because one or more lines are too long
16
packages/functions/tests/tests/support/hooks.ts
Normal file
16
packages/functions/tests/tests/support/hooks.ts
Normal 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 })
|
||||
51
packages/functions/tests/tests/support/services.ts
Normal file
51
packages/functions/tests/tests/support/services.ts
Normal 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 }
|
||||
}
|
||||
5
packages/functions/tests/tests/support/world.ts
Normal file
5
packages/functions/tests/tests/support/world.ts
Normal 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)
|
||||
14
packages/functions/tests/tsconfig.json
Normal file
14
packages/functions/tests/tsconfig.json
Normal 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"]
|
||||
}
|
||||
Reference in New Issue
Block a user