From 8e3acc8d0a919bcca77c5ecf7ece485065670419 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Thu, 26 Mar 2026 20:31:12 +0100 Subject: [PATCH] docs(agents): better (?) organization of context files --- .bruno/AGENTS.md | 65 ++++++++++++ AGENTS.md | 177 ++----------------------------- CONTRIBUTING.md | 15 ++- pkg/modules/pdfengines/AGENTS.md | 14 +++ test/integration/AGENTS.md | 77 ++++++++++++++ 5 files changed, 175 insertions(+), 173 deletions(-) create mode 100644 .bruno/AGENTS.md create mode 100644 pkg/modules/pdfengines/AGENTS.md create mode 100644 test/integration/AGENTS.md diff --git a/.bruno/AGENTS.md b/.bruno/AGENTS.md new file mode 100644 index 0000000..936c6ef --- /dev/null +++ b/.bruno/AGENTS.md @@ -0,0 +1,65 @@ +# Bruno API Collection + +A [Bruno](https://www.usebruno.com/) collection lives in `.bruno/` and mirrors every Gotenberg route. When adding or updating a route, update the collection to match. + +## Structure + +``` +.bruno/ +├── bruno.json # Collection config +├── collection.bru # Collection-level defaults (Gotenberg-Trace header) +├── environments/ +│ ├── Local.bru # baseUrl: http://localhost:3000 +│ └── Demo.bru # baseUrl: https://demo.gotenberg.dev +├── Health & Info/ # GET routes +├── Chromium/Convert/ # POST routes grouped by module +├── Chromium/Screenshot/ +├── LibreOffice/ +└── PDF Engines// # One folder per feature (Merge, Split, Rotate, …) +``` + +## `.bru` File Format + +```bru +meta { + name: + type: http + seq: +} + +post { + url: {{baseUrl}}/forms/ + body: multipartForm + auth: none +} + +body:multipart-form { + files: @file(../../test/integration/testdata/) + : + ~: +} + +headers { + ~Gotenberg-Output-Filename: + ~Gotenberg-Webhook-Url: http://localhost:8080/webhook + ~Gotenberg-Webhook-Error-Url: http://localhost:8080/webhook/error + ~Gotenberg-Webhook-Method: POST + ~Gotenberg-Webhook-Error-Method: POST + ~Gotenberg-Webhook-Extra-Http-Headers: {"X-Custom":"value"} +} +``` + +## Conventions + +- **Mandatory fields** are listed without prefix; **optional fields** are prefixed with `~` (disabled by default in Bruno). +- **File references** use relative paths to `test/integration/testdata/`. +- **Webhook and output filename headers** are included on every POST route as optional (`~`). +- **One `.bru` file per request**. For routes with read/write variants (e.g., bookmarks, metadata), create separate files in the same folder. + +## Checklist When Adding/Updating a Route + +1. Create or update the `.bru` file in the matching folder under `.bruno/`. +2. Include all form fields from the route handler — check `FormData*` calls in the route function. +3. For file upload fields (`files`, `watermark`, `stamp`, `embeds`), use `@file(...)` with a suitable test file. +4. Verify the URL path matches the route's `Path` field exactly. +5. If you add a new module folder, keep the naming consistent (e.g., `PDF Engines/Rotate/`). diff --git a/AGENTS.md b/AGENTS.md index de023ca..eaedfd8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -26,7 +26,7 @@ Implement the approved plan following the coding standards and patterns describe Write or update tests based on the plan's testing strategy: -- **Integration tests** (primary): Gherkin scenarios in `test/integration/features/`. See the [Integration Tests](#integration-tests) section. +- **Integration tests** (primary): Gherkin scenarios in `test/integration/features/`. See [`test/integration/AGENTS.md`](test/integration/AGENTS.md) for the full reference. - **Unit tests** (when applicable): Table-driven tests in `*_test.go` files using mocks from `pkg/gotenberg/mocks.go`. ### Step 4 — Review @@ -41,6 +41,8 @@ Present the review to the user and **wait for explicit approval**. Do NOT commit (): ``` +Common types: `feat`, `fix`, `refactor`, `test`, `docs`, `chore`, `ci`, `build`. The scope should match the module or area of the change (e.g., `chromium`, `pdfengines`, `api`). + Stage only the files related to the change. Do not use `git add -A` or `git add .`. --- @@ -103,16 +105,6 @@ Gotenberg uses a self-registering module architecture inspired by CaddyServer. E When adding a feature, first determine if it belongs in an existing module. Only create a new module if the feature represents a genuinely separate concern. -## Commit Convention - -Commits must follow the [Conventional Commits](https://www.conventionalcommits.org/) specification: - -``` -(): -``` - -Common types: `feat`, `fix`, `refactor`, `test`, `docs`, `chore`, `ci`, `build`. The scope should match the module or area of the change (e.g., `chromium`, `pdfengines`, `api`). - ## Coding Patterns - **Error handling:** Always wrap errors with context using `fmt.Errorf("description: %w", err)`. Never swallow errors silently. @@ -120,101 +112,6 @@ Common types: `feat`, `fix`, `refactor`, `test`, `docs`, `chore`, `ci`, `build`. - **Mocks:** Comprehensive mock implementations for all major interfaces live in `pkg/gotenberg/mocks.go`. Use these for unit tests. - **No business logic in `cmd/`:** The `cmd/gotenberg/` package is strictly for wiring and startup. -## Adding PDF Engine Features - -When adding a new PDF engine capability (e.g., bookmarks, watermark, stamp, embed), you must update the Makefile to include the corresponding engine list variable and flag. Every `--pdfengines-*-engines` flag registered in `pkg/modules/pdfengines/pdfengines.go` must have a matching entry in the Makefile: - -1. **Add a variable** in the Makefile's variable block (around line 60-70): - ```makefile - PDFENGINES__ENGINES= - ``` -2. **Add the flag** in the Makefile's command args block (around line 140-155): - ```makefile - --pdfengines--engines=$(PDFENGINES__ENGINES) \ - ``` - -The default value should match what is defined in `pdfengines.go`'s `fs.StringSlice(...)` call for that flag. - ---- - -## Integration Tests - -- **Framework:** Gherkin (BDD) via [Godog](https://github.com/cucumber/godog), with `testcontainers-go` for Docker orchestration. -- **Feature files:** `test/integration/features/*.feature` — one file per endpoint or capability. -- **Test infrastructure:** `test/integration/scenario/` — Go step definitions, container management, HTTP helpers, PDF validation. -- **Entry point:** `test/integration/main_test.go` (build tag: `integration`). -- **Test data:** `test/integration/testdata/` - -### How It Works - -Each scenario spins up a fresh Gotenberg Docker container via testcontainers. The step definitions in `scenario/scenario.go` map Gherkin steps to Go functions. An additional `gotenberg/integration-tools` container provides PDF validation tools (`verapdf`, `pdfinfo`, `pdftotext`). - -**Important:** Integration tests require a Docker image. Run `make build` before `make test-integration`. - -### Selective Test Runs - -Use the `TAGS` variable to run only relevant scenarios: - -```bash -make test-integration TAGS=health -make test-integration TAGS=chromium-convert-html -make test-integration TAGS="merge,split" -``` - -Available tags: `chromium`, `chromium-concurrent`, `chromium-convert-html`, `chromium-convert-markdown`, `chromium-convert-url`, `debug`, `health`, `libreoffice`, `libreoffice-convert`, `output-filename`, `pdfengines`, `pdfengines-convert`, `pdfengines-embed`, `embed`, `pdfengines-encrypt`, `encrypt`, `pdfengines-flatten`, `flatten`, `pdfengines-merge`, `merge`, `pdfengines-metadata`, `metadata`, `pdfengines-split`, `split`, `pdfengines-watermark`, `watermark`, `pdfengines-stamp`, `stamp`, `pdfengines-bookmarks`, `bookmarks`, `pdfengines-rotate`, `rotate`, `prometheus-metrics`, `root`, `version`, `webhook`, `download-from`. - -Other useful flags: - -```bash -make test-integration NO_CONCURRENCY=true # Disable parallel scenarios -make test-integration PLATFORM=linux/arm64 # Force a specific platform -``` - -### Writing a New Integration Test - -1. Create or update a `.feature` file in `test/integration/features/`. -2. Tag it appropriately (e.g., `@chromium @chromium-convert-html`). -3. If the feature requires new tag(s), add them to both the `TAGS` comment block in the `Makefile` and the "Available tags" list in this file. -4. If you create a new step definition, add it to `scenario/scenario.go`, register it in `InitializeScenario`, and update the "Available Gherkin Steps" list below. -5. Test data goes in `test/integration/testdata/`. - -### Available Gherkin Steps - -**Given (setup):** - -- `I have a default Gotenberg container` -- `I have a Gotenberg container with the following environment variable(s):` (table: key | value) -- `I have a (webhook|static) server` - -**When (action):** - -- `I make a "(GET|HEAD)" request to Gotenberg at the "" endpoint` -- `I make a "(GET|HEAD)" request to Gotenberg at the "" endpoint with the following header(s):` (table: name | value) -- `I make a "(POST)" request to Gotenberg at the "" endpoint with the following form data and header(s):` (table: name | value | kind — where kind is `file`, `field`, or `header`) -- `I make concurrent "(POST)" requests to Gotenberg at the "" endpoint with the following form data and header(s):` (same table format) -- `I wait for the asynchronous request to the webhook` - -**Then (assertions):** - -- `the response status code should be ` -- `the (response|webhook request) header "" should be ""` -- `the (response|webhook request) cookie "" should be ""` -- `the (response|webhook request) body should match string:` (docstring) -- `the (response|webhook request) body should contain string:` (docstring) -- `the (response|webhook request) body should match JSON:` (docstring — use `"ignore"` for dynamic values like timestamps) -- `there should be PDF(s) in the (response|webhook request)` -- `there should be the following file(s) in the (response|webhook request):` (table of filenames) -- `the "" PDF should have page(s)` -- `the "" PDF (should|should NOT) be set to landscape orientation` -- `the "" PDF (should|should NOT) have the following content at page :` (docstring) -- `the (response|webhook request) PDF(s) should be valid "" with a tolerance of failed rule(s)` (standards: `PDF/A-1b`, `PDF/A-2b`, `PDF/A-3b`, `PDF/UA-1`, `PDF/UA-2`) -- `the (response|webhook request) PDF(s) (should|should NOT) be flatten` -- `the (response|webhook request) PDF(s) (should|should NOT) be encrypted` -- `the (response|webhook request) PDF(s) (should|should NOT) have the "" file embedded` -- `the Gotenberg container (should|should NOT) log the following entries:` (table of log substrings) -- `all concurrent response status codes should be ` -- `all concurrent responses should have PDF(s)` - --- ## Review Checklist @@ -269,68 +166,10 @@ A change is ready to merge only when: --- -## Bruno API Collection +## Scoped Guidelines -A [Bruno](https://www.usebruno.com/) collection lives in `.bruno/` and mirrors every Gotenberg route. When adding or updating a route, update the collection to match. +Detailed guidelines for specific areas of the codebase live in their own `AGENTS.md` files: -### Structure - -``` -.bruno/ -├── bruno.json # Collection config -├── collection.bru # Collection-level defaults (Gotenberg-Trace header) -├── environments/ -│ ├── Local.bru # baseUrl: http://localhost:3000 -│ └── Demo.bru # baseUrl: https://demo.gotenberg.dev -├── Health & Info/ # GET routes -├── Chromium/Convert/ # POST routes grouped by module -├── Chromium/Screenshot/ -├── LibreOffice/ -└── PDF Engines// # One folder per feature (Merge, Split, Rotate, …) -``` - -### `.bru` file format - -```bru -meta { - name: - type: http - seq: -} - -post { - url: {{baseUrl}}/forms/ - body: multipartForm - auth: none -} - -body:multipart-form { - files: @file(../../test/integration/testdata/) - : - ~: -} - -headers { - ~Gotenberg-Output-Filename: - ~Gotenberg-Webhook-Url: http://localhost:8080/webhook - ~Gotenberg-Webhook-Error-Url: http://localhost:8080/webhook/error - ~Gotenberg-Webhook-Method: POST - ~Gotenberg-Webhook-Error-Method: POST - ~Gotenberg-Webhook-Extra-Http-Headers: {"X-Custom":"value"} -} -``` - -### Conventions - -- **Mandatory fields** are listed without prefix; **optional fields** are prefixed with `~` (disabled by default in Bruno). -- **File references** use relative paths to `test/integration/testdata/`. -- **Webhook and output filename headers** are included on every POST route as optional (`~`). -- **One `.bru` file per request**. For routes with read/write variants (e.g., bookmarks, metadata), create separate files in the same folder. - -### Checklist when adding/updating a route - -1. Create or update the `.bru` file in the matching folder under `.bruno/`. -2. Include all form fields from the route handler — check `FormData*` calls in the route function. -3. For file upload fields (`files`, `watermark`, `stamp`, `embeds`), use `@file(...)` with a suitable test file. -4. Verify the URL path matches the route's `Path` field exactly. -5. If you add a new module folder, keep the naming consistent (e.g., `PDF Engines/Rotate/`). +- [`test/integration/AGENTS.md`](test/integration/AGENTS.md) — Integration test framework, Gherkin step reference, available tags, and how to write new tests. +- [`.bruno/AGENTS.md`](.bruno/AGENTS.md) — Bruno API collection structure, `.bru` file format, conventions, and route update checklist. +- [`pkg/modules/pdfengines/AGENTS.md`](pkg/modules/pdfengines/AGENTS.md) — How to add new PDF engine features (Makefile variable and flag). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 645721f..9d191d8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,10 +2,6 @@ Thank you for your interest in contributing to Gotenberg! This guide will help you get started. -## Before You Start - -Please read the [AGENTS.md](AGENTS.md) file — it describes the core principles, project layout, development standards, integration test reference, review checklist, and Bruno collection guidelines that all contributions must follow. - ## Getting Started ### Prerequisites @@ -61,3 +57,14 @@ Before opening a PR, verify: - **Backward compatibility matters.** Do not rename or remove existing CLI flags, environment variables, or API form fields without discussion. - **Integration tests first.** When adding a feature or route, start by writing the Gherkin scenario in `test/integration/features/`. - **No business logic in `cmd/`.** All logic belongs in `pkg/`. + +## Detailed Guidelines + +The [`AGENTS.md`](AGENTS.md) files contain comprehensive guidelines used by both human contributors and AI-assisted tools: + +| File | What it covers | +| ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | +| [`AGENTS.md`](AGENTS.md) | Core principles, mandatory workflow, project layout, coding patterns, module system, Makefile reference, review checklist | +| [`test/integration/AGENTS.md`](test/integration/AGENTS.md) | Integration test framework (Godog/Gherkin), available tags, step reference, how to write new tests | +| [`.bruno/AGENTS.md`](.bruno/AGENTS.md) | Bruno API collection structure, `.bru` file format, conventions, route update checklist | +| [`pkg/modules/pdfengines/AGENTS.md`](pkg/modules/pdfengines/AGENTS.md) | How to add new PDF engine features (Makefile variable and flag) | diff --git a/pkg/modules/pdfengines/AGENTS.md b/pkg/modules/pdfengines/AGENTS.md new file mode 100644 index 0000000..baca3fd --- /dev/null +++ b/pkg/modules/pdfengines/AGENTS.md @@ -0,0 +1,14 @@ +# Adding PDF Engine Features + +When adding a new PDF engine capability (e.g., bookmarks, watermark, stamp, embed), you must update the Makefile to include the corresponding engine list variable and flag. Every `--pdfengines-*-engines` flag registered in `pkg/modules/pdfengines/pdfengines.go` must have a matching entry in the Makefile: + +1. **Add a variable** in the Makefile's variable block (around line 60-70): + ```makefile + PDFENGINES__ENGINES= + ``` +2. **Add the flag** in the Makefile's command args block (around line 140-155): + ```makefile + --pdfengines--engines=$(PDFENGINES__ENGINES) \ + ``` + +The default value should match what is defined in `pdfengines.go`'s `fs.StringSlice(...)` call for that flag. diff --git a/test/integration/AGENTS.md b/test/integration/AGENTS.md new file mode 100644 index 0000000..c002ddd --- /dev/null +++ b/test/integration/AGENTS.md @@ -0,0 +1,77 @@ +# Integration Tests + +- **Framework:** Gherkin (BDD) via [Godog](https://github.com/cucumber/godog), with `testcontainers-go` for Docker orchestration. +- **Feature files:** `test/integration/features/*.feature` — one file per endpoint or capability. +- **Test infrastructure:** `test/integration/scenario/` — Go step definitions, container management, HTTP helpers, PDF validation. +- **Entry point:** `test/integration/main_test.go` (build tag: `integration`). +- **Test data:** `test/integration/testdata/` + +## How It Works + +Each scenario spins up a fresh Gotenberg Docker container via testcontainers. The step definitions in `scenario/scenario.go` map Gherkin steps to Go functions. An additional `gotenberg/integration-tools` container provides PDF validation tools (`verapdf`, `pdfinfo`, `pdftotext`). + +**Important:** Integration tests require a Docker image. Run `make build` before `make test-integration`. + +## Selective Test Runs + +Use the `TAGS` variable to run only relevant scenarios: + +```bash +make test-integration TAGS=health +make test-integration TAGS=chromium-convert-html +make test-integration TAGS="merge,split" +``` + +Available tags: `chromium`, `chromium-concurrent`, `chromium-convert-html`, `chromium-convert-markdown`, `chromium-convert-url`, `debug`, `health`, `libreoffice`, `libreoffice-convert`, `output-filename`, `pdfengines`, `pdfengines-convert`, `pdfengines-embed`, `embed`, `pdfengines-encrypt`, `encrypt`, `pdfengines-flatten`, `flatten`, `pdfengines-merge`, `merge`, `pdfengines-metadata`, `metadata`, `pdfengines-split`, `split`, `pdfengines-watermark`, `watermark`, `pdfengines-stamp`, `stamp`, `pdfengines-bookmarks`, `bookmarks`, `pdfengines-rotate`, `rotate`, `prometheus-metrics`, `root`, `version`, `webhook`, `download-from`. + +Other useful flags: + +```bash +make test-integration NO_CONCURRENCY=true # Disable parallel scenarios +make test-integration PLATFORM=linux/arm64 # Force a specific platform +``` + +## Writing a New Integration Test + +1. Create or update a `.feature` file in `test/integration/features/`. +2. Tag it appropriately (e.g., `@chromium @chromium-convert-html`). +3. If the feature requires new tag(s), add them to both the `TAGS` comment block in the `Makefile` and the "Available tags" list above. +4. If you create a new step definition, add it to `scenario/scenario.go`, register it in `InitializeScenario`, and update the "Available Gherkin Steps" list below. +5. Test data goes in `test/integration/testdata/`. + +## Available Gherkin Steps + +**Given (setup):** + +- `I have a default Gotenberg container` +- `I have a Gotenberg container with the following environment variable(s):` (table: key | value) +- `I have a (webhook|static) server` + +**When (action):** + +- `I make a "(GET|HEAD)" request to Gotenberg at the "" endpoint` +- `I make a "(GET|HEAD)" request to Gotenberg at the "" endpoint with the following header(s):` (table: name | value) +- `I make a "(POST)" request to Gotenberg at the "" endpoint with the following form data and header(s):` (table: name | value | kind — where kind is `file`, `field`, or `header`) +- `I make concurrent "(POST)" requests to Gotenberg at the "" endpoint with the following form data and header(s):` (same table format) +- `I wait for the asynchronous request to the webhook` + +**Then (assertions):** + +- `the response status code should be ` +- `the (response|webhook request) header "" should be ""` +- `the (response|webhook request) cookie "" should be ""` +- `the (response|webhook request) body should match string:` (docstring) +- `the (response|webhook request) body should contain string:` (docstring) +- `the (response|webhook request) body should match JSON:` (docstring — use `"ignore"` for dynamic values like timestamps) +- `there should be PDF(s) in the (response|webhook request)` +- `there should be the following file(s) in the (response|webhook request):` (table of filenames) +- `the "" PDF should have page(s)` +- `the "" PDF (should|should NOT) be set to landscape orientation` +- `the "" PDF (should|should NOT) have the following content at page :` (docstring) +- `the (response|webhook request) PDF(s) should be valid "" with a tolerance of failed rule(s)` (standards: `PDF/A-1b`, `PDF/A-2b`, `PDF/A-3b`, `PDF/UA-1`, `PDF/UA-2`) +- `the (response|webhook request) PDF(s) (should|should NOT) be flatten` +- `the (response|webhook request) PDF(s) (should|should NOT) be encrypted` +- `the (response|webhook request) PDF(s) (should|should NOT) have the "" file embedded` +- `the Gotenberg container (should|should NOT) log the following entries:` (table of log substrings) +- `all concurrent response status codes should be ` +- `all concurrent responses should have PDF(s)`