From 92d7bd54228ed9de7d57f517594cf70a550d92c6 Mon Sep 17 00:00:00 2001 From: Pierre Brisorgueil Date: Mon, 1 Jun 2026 21:19:40 +0200 Subject: [PATCH] feat(tasks): add operationId fields to OpenAPI operations for Redoc deep-linking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Promotes operationId fields from trawl_node to devkit. Every operation in modules/tasks/doc/tasks.yml now has a unique camelCase operationId (getTaskStats, listTasks, createTask, getTask, updateTask, deleteTask), enabling stable per-operation deep links in Redoc and compatibility with API doc generators. Purely additive — no behavior change. Adds tasks.openapi-operationid.unit.tests.js to assert every operation has a defined, unique operationId. Part of infra plan 2026-06-01-trawl-promote-up-followups.md Task 3. --- modules/tasks/doc/tasks.yml | 6 ++++ .../tasks.openapi-operationid.unit.tests.js | 29 +++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 modules/tasks/tests/tasks.openapi-operationid.unit.tests.js diff --git a/modules/tasks/doc/tasks.yml b/modules/tasks/doc/tasks.yml index d2b6aebee..4df06707b 100644 --- a/modules/tasks/doc/tasks.yml +++ b/modules/tasks/doc/tasks.yml @@ -7,6 +7,7 @@ paths: get: tags: - Tasks + operationId: getTaskStats summary: Get task statistics description: Returns the estimated total number of tasks. Public endpoint — no authentication required. responses: @@ -29,6 +30,7 @@ paths: get: tags: - Tasks + operationId: listTasks summary: List tasks description: Returns all tasks scoped to the current organization. Requires authentication. security: @@ -59,6 +61,7 @@ paths: post: tags: - Tasks + operationId: createTask summary: Create a task description: Creates a new task scoped to the current organization. The requesting user is set as the task owner. security: @@ -94,6 +97,7 @@ paths: get: tags: - Tasks + operationId: getTask summary: Get a task description: Returns a single task by ID. Ownership is enforced by CASL policy. security: @@ -123,6 +127,7 @@ paths: put: tags: - Tasks + operationId: updateTask summary: Update a task description: Updates an existing task. All fields are optional — only provided fields are updated. Ownership is enforced by CASL policy. security: @@ -160,6 +165,7 @@ paths: delete: tags: - Tasks + operationId: deleteTask summary: Delete a task description: Deletes a task by ID. Ownership is enforced by CASL policy. security: diff --git a/modules/tasks/tests/tasks.openapi-operationid.unit.tests.js b/modules/tasks/tests/tasks.openapi-operationid.unit.tests.js new file mode 100644 index 000000000..1d5a766e6 --- /dev/null +++ b/modules/tasks/tests/tasks.openapi-operationid.unit.tests.js @@ -0,0 +1,29 @@ +import { describe, test, expect } from '@jest/globals'; +import yaml from 'js-yaml'; +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +describe('modules/tasks/doc/tasks.yml — OpenAPI operationIds:', () => { + const specPath = path.resolve(__dirname, '../doc/tasks.yml'); + const spec = yaml.load(fs.readFileSync(specPath, 'utf8')); + + test('every operation has a unique operationId', () => { + const operationIds = []; + for (const [, pathItem] of Object.entries(spec.paths ?? {})) { + for (const [method, operation] of Object.entries(pathItem)) { + if (['get', 'post', 'put', 'patch', 'delete'].includes(method)) { + expect(operation.operationId).toBeDefined(); + expect(typeof operation.operationId).toBe('string'); + operationIds.push(operation.operationId); + } + } + } + // Uniqueness check + expect(new Set(operationIds).size).toBe(operationIds.length); + // At least one operationId (tasks.yml is not empty) + expect(operationIds.length).toBeGreaterThan(0); + }); +});