chore: server-and-serverless template

This commit is contained in:
e2e
2026-06-28 12:36:05 +02:00
commit b21fe5a987
202 changed files with 9063 additions and 0 deletions

View File

@@ -0,0 +1 @@
NODE_ENV=test

View File

@@ -0,0 +1,25 @@
{
"name": "function-tests",
"version": "0.0.1",
"private": true,
"type": "module",
"description": "Function test harness — Cucumber over in-process Pikku RPC with stubbed services.",
"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/cucumber": "^0.12.5",
"@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",
"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,24 @@
Feature: Starter message
Background:
Given "alice" has session:
| userId | user-001 |
Scenario: authenticated user can read the starter message
When "alice" calls "getMessage"
Then the call succeeds
And the result has "message"
Scenario: authenticated user can update the starter message
When "alice" calls "updateMessage" with:
| message | Hello from tests |
Then the call succeeds
And the result has "message"
Scenario: updated message is reflected in subsequent reads
When "alice" calls "updateMessage" with:
| message | Persisted value |
Then the call succeeds
When "alice" calls "getMessage"
Then the call succeeds
And the result "message" is "Persisted value"

View File

@@ -0,0 +1,7 @@
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,49 @@
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 '../../../src/db.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/sqlite'),
seedFile: repoRoot('../../db/sqlite-test-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, dev: { db: { file: dbFile } } } as never,
injected as never,
)
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"]
}