Sub-skills
Testing in depth
For your agentCopy this and give it to the AI you're building with.
Run `cubek skill`, then `cubek skill testing`, read all of both and follow them exactly (no CLI: download https://cubek.dev/SKILL.md and https://cubek.dev/skills/testing.md verbatim with curl; don't summarize them). Run `cubek check` after every edit until it is clean.
What to test and how: pure logic with cubek test (bun), routes and data
with cubek dev + curl, and cubek check after every edit.
Unit tests (cubek test)
*.test.ts files beside the code run under bun and are never deployed.
validate, crypto, encoding, csv, fmt, markdown work in tests;
I/O primitives (db, kv, storage, fetch, mail, task) throw. So
keep decisions in pure functions and let routes do the I/O.
// src/lib/pricing.ts
import { validate, type Infer } from "cubek"
export const cartInput = validate.object({
items: validate.array(validate.object({ sku: validate.string({ min: 1 }), qty: validate.number({ int: true, min: 1, max: 99 }), cents: validate.number({ int: true, min: 0 }) }), { min: 1 }),
coupon: validate.string().optional(),
})
export type Cart = Infer<typeof cartInput>
export function total(cart: Cart): { subtotal: number; discount: number; total: number } {
let subtotal = 0
for (const it of cart.items) subtotal += it.qty * it.cents
const discount = cart.coupon === "TEN" ? Math.round(subtotal / 10) : 0
return { subtotal, discount, total: subtotal - discount }
}
export function slugify(s: string): string {
return s.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "")
}// src/lib/pricing.test.ts
import { describe, expect, test } from "bun:test"
import { cartInput, slugify, total } from "./pricing"
describe("total", () => {
test("applies the coupon", () => {
const cart = cartInput.parse({ items: [{ sku: "a", qty: 2, cents: 500 }], coupon: "TEN" })
expect(total(cart)).toEqual({ subtotal: 1000, discount: 100, total: 900 })
})
test("rejects an empty cart", () => {
expect(cartInput.safeParse({ items: [] }).success).toBe(false)
})
})
test("slugify", () => {
expect(slugify(" Hello, World! ")).toBe("hello-world")
})cubek test # every *.test.ts
cubek test -- pricing # bun args after --: a file filterRoutes, data and tasks (cubek dev + curl)
The dev server is the production runtime with a local SQLite: exercise the real routes.
cubek dev --json & # prints {"ok":true,"url":...} once it listens
curl -s localhost:8787/api/items
curl -s -X POST localhost:8787/api/items -H 'content-type: application/json' -d '{"name":"pen","qty":3}'
curl -s -c jar -b jar -X POST localhost:8787/auth/signin -d 'email=a@b.co&password=secret123' # a form, keeps the cookie
curl -s -b jar localhost:8787/admin/users
cubek sql --dev "SELECT COUNT(*) AS n FROM items" # the effect in the database
cubek task run cleanup # a task or cron now
cubek dev --stop # this project's server only- Check status codes too:
curl -s -o /dev/null -w '%{http_code}\n' .... - The dev output shows each request, logs, errors and
slow queries with the missing index.
GET /_cubek/stats: per-route latency. - Mail lands in
.cubek/mail/*.eml; dev secrets in.cubek/secrets.json(cubek secrets --dev set NAME VALUE). - Reset the dev data: stop the server, delete
.cubek/dev.sqlite*(or seed withcubek sql --dev --writeusing fixed ids andINSERT OR IGNORE).
After a deploy
cubek deploy # a preview: https://<name>--r<N>.cubek.app with a copy of production's data
curl -s https://<name>--r<N>.cubek.app/healthz
cubek logs --level warn # 500s carry their error here
cubek deploy --prodA GET /healthz route answering ok makes smoke checks one line.