-
-
Notifications
You must be signed in to change notification settings - Fork 5
initial e2e tests #113
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
initial e2e tests #113
Changes from all commits
Commits
Show all changes
26 commits
Select commit
Hold shift + click to select a range
7054c79
init e2e app
20jasper 4e3aeb9
update tsconfig
20jasper 184b3f0
initial smoke test
20jasper 30ccbee
type safe hoof client
20jasper eb4c1fe
Hit all healthcheck endpoints
20jasper f4725de
expose port from app
20jasper ec10e38
remove silly path that nx added
20jasper 26c078e
Add agents file
20jasper dac0141
use nx instead of npm for command orchestration, fix wacky imports, c…
20jasper 927d1aa
avoid server startup side effect when importing api startup
20jasper 71c2915
Only run typegen when the api changes
20jasper 04665d6
update github action and scripts
20jasper 44c4e85
Make knip happy by removing unused deps
20jasper 6c05356
Wait for docker compose up in CI and load env
20jasper 3f610a9
Change to nodenext
20jasper 603be67
Don't swallow error for generate types
20jasper 389ceb1
Move more reused deps to the catalog
20jasper add75af
Formatting
20jasper 0512a2a
Upgrade to node 24
20jasper 8147267
update lockfile
20jasper 5da33e9
remove unused package
20jasper 6df7d77
Removed unused generated files and libraries. Upgrade nx version to s…
20jasper 9b82d3f
migrate away from deprecated vite nx test runner
20jasper b465c52
format
20jasper e4cc443
Merge branch 'main' into fork-20jasper/e2e-init
fennifith 1e03bf5
update devcontainer node version to 24
fennifith File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1 @@ | ||
| v22.15.0 | ||
| v24 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| <!-- nx configuration start--> | ||
| <!-- Leave the start & end comments to automatically receive updates. --> | ||
|
|
||
| # General Guidelines for working with Nx | ||
|
|
||
| - When running tasks (for example build, lint, test, e2e, etc.), always prefer running the task through `nx` (i.e. `nx run`, `nx run-many`, `nx affected`) instead of using the underlying tooling directly | ||
| - You have access to the Nx MCP server and its tools, use them to help the user | ||
| - When answering questions about the repository, use the `nx_workspace` tool first to gain an understanding of the workspace architecture where applicable. | ||
| - When working in individual projects, use the `nx_project_details` mcp tool to analyze and understand the specific project structure and dependencies | ||
| - For questions around nx configuration, best practices or if you're unsure, use the `nx_docs` tool to get relevant, up-to-date docs. Always use this instead of assuming things about nx configuration | ||
| - If the user needs help with an Nx configuration or project graph error, use the `nx_workspace` tool to get any errors | ||
| - For Nx plugin best practices, check `node_modules/@nx/<plugin>/PLUGIN.md`. Not all plugins have this file - proceed without it if unavailable. | ||
|
|
||
| <!-- nx configuration end--> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import rateLimit from "./plugins/rate-limit/index.ts"; | ||
| import sensible from "./plugins/sensible.ts"; | ||
| import swagger from "./plugins/swagger.ts"; | ||
| import { healthRoutes } from "./routes/health.ts"; | ||
| import postImagesRoutes from "./routes/tasks/post-images.ts"; | ||
| import urlMetadataRoutes from "./routes/tasks/url-metadata.ts"; | ||
| import fastify from "fastify"; | ||
|
|
||
| export const createApp = () => { | ||
| const app = fastify({ | ||
| logger: true, | ||
| }); | ||
|
|
||
| app.register(rateLimit); | ||
| app.register(sensible); | ||
| app.register(swagger); | ||
| app.register(healthRoutes); | ||
| app.register(postImagesRoutes); | ||
| app.register(urlMetadataRoutes); | ||
|
|
||
| return app; | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,23 +1,6 @@ | ||
| import { env } from "@playfulprogramming/common"; | ||
| import rateLimit from "./plugins/rate-limit/index.ts"; | ||
| import sensible from "./plugins/sensible.ts"; | ||
| import swagger from "./plugins/swagger.ts"; | ||
| import { healthRoutes } from "./routes/health.ts"; | ||
| import postImagesRoutes from "./routes/tasks/post-images.ts"; | ||
| import urlMetadataRoutes from "./routes/tasks/url-metadata.ts"; | ||
| import fastify from "fastify"; | ||
| import { createApp } from "./createApp.ts"; | ||
|
|
||
| const app = fastify({ | ||
| logger: true, | ||
| }); | ||
|
|
||
| app.register(rateLimit); | ||
| app.register(sensible); | ||
| app.register(swagger); | ||
| app.register(healthRoutes); | ||
| app.register(postImagesRoutes); | ||
| app.register(urlMetadataRoutes); | ||
|
|
||
| app.listen({ port: env.PORT, host: "0.0.0.0" }, (err) => { | ||
| createApp().listen({ port: env.PORT, host: "0.0.0.0" }, (err) => { | ||
| if (err) throw err; | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import baseConfig from "../../eslint.config.mjs"; | ||
|
|
||
| export default [ | ||
| ...baseConfig, | ||
| { | ||
| files: ["**/*.json"], | ||
| rules: { | ||
| "@nx/dependency-checks": [ | ||
| "error", | ||
| { | ||
| ignoredFiles: [ | ||
| "{projectRoot}/eslint.config.{js,cjs,mjs,ts,cts,mts}", | ||
| "{projectRoot}/vite.config.{js,ts,mjs,mts}", | ||
| ], | ||
| }, | ||
| ], | ||
| }, | ||
| languageOptions: { | ||
| parser: await import("jsonc-eslint-parser"), | ||
| }, | ||
| }, | ||
| ]; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| { | ||
| "name": "e2e", | ||
| "version": "0.0.1", | ||
| "private": true, | ||
| "type": "module", | ||
| "scripts": { | ||
| "typegen": "node --experimental-strip-types scripts/typegen.ts", | ||
| "test": "vitest run" | ||
| }, | ||
| "dependencies": { | ||
| "@playfulprogramming/api": "workspace:*", | ||
| "openapi-fetch": "catalog:" | ||
| }, | ||
| "devDependencies": { | ||
| "openapi-typescript": "catalog:", | ||
| "vitest": "catalog:" | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| { | ||
| "name": "e2e", | ||
| "$schema": "../../node_modules/nx/schemas/project-schema.json", | ||
| "sourceRoot": "apps/e2e/src", | ||
| "projectType": "application", | ||
| "tags": [], | ||
| "targets": { | ||
| "lint": { | ||
| "executor": "nx:run-commands", | ||
| "options": { | ||
| "command": "eslint ./src", | ||
| "cwd": "apps/e2e" | ||
| } | ||
| }, | ||
| "typegen": { | ||
| "executor": "nx:run-commands", | ||
| "options": { | ||
| "command": "node --experimental-strip-types scripts/typegen.ts", | ||
| "cwd": "apps/e2e" | ||
| }, | ||
| "outputs": ["{projectRoot}/src/generated/api-schema.d.ts"], | ||
| "cache": true, | ||
| "inputs": ["{workspaceRoot}/apps/api/**/*"] | ||
| }, | ||
| "test": { | ||
| "executor": "@nx/vitest:test", | ||
| "outputs": ["{options.reportsDirectory}"], | ||
| "options": { | ||
| "reportsDirectory": "../../coverage/apps/e2e" | ||
| }, | ||
| "dependsOn": ["typegen"] | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| import fs from "fs/promises"; | ||
| import path from "path"; | ||
| import { fileURLToPath } from "url"; | ||
| import { execSync } from "child_process"; | ||
| import { spawnApp } from "../src/lib/spawn-app.ts"; | ||
|
|
||
| const __filename = fileURLToPath(import.meta.url); | ||
| const __dirname = path.dirname(__filename); | ||
|
|
||
| const outputDir = path.join(__dirname, "../src/generated"); | ||
| const outputFile = path.join(outputDir, "api-schema.d.ts"); | ||
|
|
||
| export default async function generateTypes() { | ||
| await fs.mkdir(outputDir, { recursive: true }); | ||
|
|
||
| await using app = await spawnApp(); | ||
| const swaggerUrl = `${app.baseUrl}/openapi.json`; | ||
|
|
||
| const response = await fetch(swaggerUrl); | ||
| if (!response.ok) { | ||
| throw new Error( | ||
| `Failed to fetch OpenAPI spec: ${response.status} ${response.statusText}`, | ||
| ); | ||
| } | ||
| const spec = await response.json(); | ||
|
|
||
| console.log("Generating types"); | ||
| const tempSpecFile = path.join(outputDir, ".api-spec.json"); | ||
| try { | ||
| await fs.writeFile(tempSpecFile, JSON.stringify(spec, null, 0)); | ||
| execSync( | ||
| `pnpm exec openapi-typescript "${tempSpecFile}" -o "${outputFile}"`, | ||
| { | ||
| stdio: "inherit", | ||
| }, | ||
| ); | ||
| } finally { | ||
| await fs.unlink(tempSpecFile); | ||
| } | ||
|
|
||
| console.log(`Types generated successfully at ${outputFile}`); | ||
| console.log( | ||
| `You may need to restart your TypeScript language server for changes to reflect`, | ||
| ); | ||
| } | ||
|
|
||
| if (process.argv[1] === __filename) { | ||
| generateTypes() | ||
| .then(() => process.exit(0)) | ||
| .catch((err) => { | ||
| console.error("generateTypes failed:", err); | ||
| process.exit(1); | ||
| }); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| import createClient, { type Client } from "openapi-fetch"; | ||
| import type { paths } from "../generated/api-schema.d.ts"; | ||
| import { createApp } from "@playfulprogramming/api/src/createApp.ts"; | ||
|
|
||
| type TestApp = { | ||
| baseUrl: string; | ||
| port: number; | ||
| } & AsyncDisposable; | ||
|
|
||
| type TestAppWithClient = TestApp & { | ||
| client: Client<paths>; | ||
| } & AsyncDisposable; | ||
|
|
||
| export async function spawnApp(): Promise<TestApp> { | ||
| const app = createApp(); | ||
|
|
||
| await app.listen({ port: 0, host: "127.0.0.1" }); | ||
|
|
||
| const address = app.server.address(); | ||
| const port = typeof address === "string" ? address : address?.port; | ||
| if (!port) throw new Error("Failed to get server port"); | ||
|
|
||
| const baseUrl = `http://127.0.0.1:${port}`; | ||
| return { | ||
| baseUrl, | ||
| port: Number(port), | ||
| [Symbol.asyncDispose]: async () => { | ||
| await app.close(); | ||
| }, | ||
| }; | ||
| } | ||
|
|
||
| export async function spawnAppWithClient(): Promise<TestAppWithClient> { | ||
| const app = await spawnApp(); | ||
|
|
||
| const client = createClient<paths>({ | ||
| baseUrl: app.baseUrl, | ||
| }); | ||
|
|
||
| return { | ||
| ...app, | ||
| client, | ||
| [Symbol.asyncDispose]: async () => { | ||
| await app[Symbol.asyncDispose](); | ||
| }, | ||
| }; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| import { describe, it, expect } from "vitest"; | ||
| import { spawnAppWithClient } from "./lib/spawn-app.ts"; | ||
|
|
||
| describe("E2E: Health Check", () => { | ||
| it.each(["/", "/health/postgres", "/health/redis"] as const)( | ||
| "should respond 200 for %s", | ||
| async (path) => { | ||
| await using app = await spawnAppWithClient(); | ||
|
|
||
| const res = await app.client.GET(path, { | ||
| parseAs: "text", | ||
| }); | ||
| expect(res.response.status).toBe(200); | ||
| expect(res.data).toBe("OK"); | ||
| }, | ||
| ); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| { | ||
| "extends": "../../tsconfig.json", | ||
| "compilerOptions": { | ||
| "module": "NodeNext", | ||
| "forceConsistentCasingInFileNames": true, | ||
| "strict": true, | ||
| "noImplicitOverride": true, | ||
| "noImplicitReturns": true, | ||
| "noFallthroughCasesInSwitch": true, | ||
| "noPropertyAccessFromIndexSignature": true, | ||
| "verbatimModuleSyntax": true, | ||
| "isolatedModules": true, | ||
| "moduleDetection": "force", | ||
| "noUncheckedIndexedAccess": true, | ||
| "allowJs": true, | ||
| "resolveJsonModule": true | ||
| }, | ||
| "include": ["src/**/*", "scripts/**/*"] | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| import { defineConfig } from "vite"; | ||
| import { nxViteTsPaths } from "@nx/vite/plugins/nx-tsconfig-paths.plugin"; | ||
| import { nxCopyAssetsPlugin } from "@nx/vite/plugins/nx-copy-assets.plugin"; | ||
|
|
||
| export default defineConfig(() => ({ | ||
| root: __dirname, | ||
| cacheDir: "../../node_modules/.vite/apps/e2e", | ||
| plugins: [nxViteTsPaths(), nxCopyAssetsPlugin(["*.md"])], | ||
| // Uncomment this if you are using workers. | ||
| // worker: { | ||
| // plugins: [ nxViteTsPaths() ], | ||
| // }, | ||
| test: { | ||
| name: "e2e", | ||
| watch: false, | ||
| globals: true, | ||
| environment: "node", | ||
| include: ["src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"], | ||
| reporters: ["default"], | ||
| coverage: { | ||
| reportsDirectory: "../../coverage/apps/e2e", | ||
| provider: "v8" as const, | ||
| }, | ||
| }, | ||
| })); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
Node.js 22 explicit resource management await using support version💡 Result:
Node.js 22.x does not support the Explicit Resource Management syntax (
using/await using) because Node 22 ships with V8 12.4. [1]Support arrives with Node.js 24.0.0 (released May 6, 2025), which upgrades to V8 13.6 and lists “Explicit resource management” among the newly supported JavaScript features—this is the first Node line where
await usingis available natively. [2][3]Sources: [1] [2] [3]
🌐 Web query:
Node.js Symbol.asyncDispose support which version💡 Result:
Symbol.asyncDisposelanded in Node.js v20.4.0 (initialSymbol.dispose/Symbol.asyncDisposesupport was added there). [1] Core APIs then started exposing[Symbol.asyncDispose]()methods (for examplehttps.Server[Symbol.asyncDispose]()is marked “Added in: v20.4.0”). [2]So: Node.js v20.4.0+.
Sources: [1] [2]
🌐 Web query:
Node.js --experimental-strip-types flag purpose and capabilities💡 Result:
--experimental-strip-typesenables Node.js to execute.tsfiles by “erasing” (stripping) TypeScript type syntax at runtime, turning the file into valid JavaScript without doing full TypeScript compilation. It was added in Node v22.6.0. [1][2]What it does
tsc/a typechecker for that). [1]What it does not do / key limits
enumandnamespace/modulewith runtime code; those require--experimental-transform-types. [1][3]tsconfig.json-dependent behaviors like path mapping (paths) or downleveling newer JS syntax. [1]type-only imports to be marked withtype, and being disabled for TypeScript insidenode_modulesby default (at least at introduction). [2]Related flags / current behavior
--experimental-transform-typesextends support to TypeScript syntax that needs transformation/codegen (e.g., enums/namespaces). [1]--no-experimental-strip-typesto disable running TypeScript via stripping. [4]Sources: Node.js TypeScript docs and release notes; TypeScript team notes on “erasable” syntax expectations. [1][2][3][4]
🏁 Script executed:
Repository: playfulprogramming/hoof
Length of output: 49
🏁 Script executed:
Repository: playfulprogramming/hoof
Length of output: 109
🏁 Script executed:
Repository: playfulprogramming/hoof
Length of output: 94
🏁 Script executed:
Repository: playfulprogramming/hoof
Length of output: 151
🏁 Script executed:
Repository: playfulprogramming/hoof
Length of output: 1244
🏁 Script executed:
#!/bin/bash cat -n apps/e2e/src/lib/spawn-app.tsRepository: playfulprogramming/hoof
Length of output: 1489
await usingsyntax is not supported by the Node.js runtime (22.x ships with V8 12.4), causing a pipeline SyntaxError.The
await usingdeclaration is a TC39 Explicit Resource Management proposal that only landed in Node.js 24.0.0 (V8 13.6). Since theenginesfield requires Node >=22.6.0, this code will fail withSyntaxError: Unexpected identifier 'app'. The--experimental-strip-typesflag only removes TypeScript type annotations—it does not enable unsupported JavaScript syntax proposals.Refactor to use a
try/finallyblock with explicit cleanup viaSymbol.asyncDispose:🐛 Proposed fix
🧰 Tools
🪛 GitHub Actions: Test
[error] 16-16: SyntaxError: Unexpected identifier 'app'.
🤖 Prompt for AI Agents