mirror of
https://github.com/Sea-Haven-Industries/meal-order-manager.git
synced 2026-09-30 04:13:12 +00:00
feat(api): add OpenAPI 3.1 and Redocly lint in CI (DEV-289)
Same extends: recommended ruleset and @redocly/cli 2.52.1 as
internal-portal. Documents current { error: string } JSON errors.
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
This commit is contained in:
parent
402541613b
commit
198269db5b
8 changed files with 711 additions and 2 deletions
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
|
|
@ -34,6 +34,9 @@ jobs:
|
|||
- name: Check template JavaScript
|
||||
run: npm run check:templates
|
||||
|
||||
- name: Lint OpenAPI
|
||||
run: npm run openapi:lint
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
|
|
|
|||
7
.redocly.yaml
Normal file
7
.redocly.yaml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# Same Redocly recommended ruleset as internal-portal (DEV-223 / DEV-289).
|
||||
extends:
|
||||
- recommended
|
||||
|
||||
apis:
|
||||
meals@v1:
|
||||
root: openapi.yaml
|
||||
|
|
@ -74,6 +74,8 @@ Publish routes are omitted from CloudFront. The generated form uses relative
|
|||
Workspace: `meal-order-manager-prod` / `meal-order-manager-dev` (us-east-1)
|
||||
|
||||
- **ECS Fargate** — Flask + gunicorn + SQS job consumer. Desired count 2 in prod, 1 in dev.
|
||||
- **VPC** — Prod attaches to the After Hours VPC (`existing_vpc_id` / `existing_public_subnet_ids` from afterhours-shift-manager outputs). The 10.60 CIDR is unused fallback.
|
||||
- **HTTP contract** — `openapi.yaml`, linted in CI with `npm run openapi:lint` (Redocly `extends: recommended`, same as internal-portal and afterhours-shift-manager).
|
||||
- **ALB** — origin for CloudFront `/api` behaviors and weekly-menu HMAC publish. Idle timeout 120s.
|
||||
- **ECR** — API image. GitHub Actions `deploy-api.yaml` owns the image; Terraform ignores `container_definitions`.
|
||||
- **S3** — `meal-order-manager-form-*` (static form hosting), `meal-order-manager-reports-*` (CSV reports + weekly summary PDF)
|
||||
|
|
@ -186,9 +188,11 @@ meal-order-manager/
|
|||
├── .github/workflows/
|
||||
│ ├── weekly-menu.yml # Monday cron: scrape + HMAC publish + notify
|
||||
│ ├── deploy-api.yaml # Image CD to Fargate
|
||||
│ ├── ci.yml # PR checks
|
||||
│ ├── ci.yml # PR checks (pytest, template JS, OpenAPI lint)
|
||||
│ └── ci-terraform.yaml # terraform fmt / validate
|
||||
├── terraform/ # HCP Terraform (cluster, ALB, ECR, jobs queue)
|
||||
├── openapi.yaml # Employee HTTP contract (Redocly recommended)
|
||||
├── .redocly.yaml
|
||||
├── Dockerfile
|
||||
├── src/
|
||||
│ ├── scraper/ # Playwright menu scraper
|
||||
|
|
|
|||
651
openapi.yaml
Normal file
651
openapi.yaml
Normal file
|
|
@ -0,0 +1,651 @@
|
|||
openapi: 3.1.0
|
||||
info:
|
||||
title: Meal Order Manager
|
||||
version: 0.1.0
|
||||
description: >
|
||||
Flask HTTP API on ECS Fargate behind orders.seahaven.com. The internal
|
||||
portal SPA calls menu, submit, and admin with a Cognito ID token from
|
||||
GET /api/auth/meals-token. Weekly-menu GitHub Actions uses HMAC publish
|
||||
routes that CloudFront does not expose. JSON errors are currently
|
||||
`{ error: string }`. Health matches the portal BFF `{ stage, sha }`.
|
||||
Lint with the same Redocly `extends: recommended` config as
|
||||
internal-portal and afterhours-shift-manager.
|
||||
contact:
|
||||
name: Sea Haven Engineering
|
||||
license:
|
||||
name: Proprietary
|
||||
identifier: LicenseRef-SeaHaven
|
||||
|
||||
servers:
|
||||
- url: /
|
||||
description: orders.seahaven.com CloudFront / local Flask :5050
|
||||
|
||||
tags:
|
||||
- name: Runtime
|
||||
description: Unauthenticated health
|
||||
- name: Menu
|
||||
description: Public weekly menu and form status
|
||||
- name: Orders
|
||||
description: Employee submit and own-order lookup
|
||||
- name: Admin
|
||||
description: Admin order edit and summary PDF
|
||||
- name: Publish
|
||||
description: Weekly-menu HMAC publish (not on CloudFront)
|
||||
|
||||
paths:
|
||||
/api/health:
|
||||
get:
|
||||
operationId: getHealth
|
||||
tags: [Runtime]
|
||||
summary: Runtime health
|
||||
security: []
|
||||
responses:
|
||||
"200":
|
||||
description: Process is up
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Health"
|
||||
|
||||
/api/menu/{week}:
|
||||
get:
|
||||
operationId: getMenu
|
||||
tags: [Menu]
|
||||
summary: Published menu for a week
|
||||
security: []
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WeekPath"
|
||||
responses:
|
||||
"200":
|
||||
description: Menu payload
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/MenuPayload"
|
||||
"400":
|
||||
$ref: "#/components/responses/StringError"
|
||||
"404":
|
||||
$ref: "#/components/responses/StringError"
|
||||
|
||||
/api/form-status/{week}:
|
||||
get:
|
||||
operationId: getFormStatus
|
||||
tags: [Menu]
|
||||
summary: Open or closed for a week
|
||||
security: []
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WeekPath"
|
||||
responses:
|
||||
"200":
|
||||
description: Form status
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/FormStatus"
|
||||
|
||||
/api/submit-order:
|
||||
post:
|
||||
operationId: submitOrder
|
||||
tags: [Orders]
|
||||
summary: Place or replace this week's order
|
||||
security:
|
||||
- portalCognito: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/SubmitBody"
|
||||
responses:
|
||||
"200":
|
||||
description: Order saved
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/SubmitResult"
|
||||
"400":
|
||||
$ref: "#/components/responses/StringError"
|
||||
"403":
|
||||
$ref: "#/components/responses/StringError"
|
||||
"404":
|
||||
$ref: "#/components/responses/StringError"
|
||||
"410":
|
||||
$ref: "#/components/responses/StringError"
|
||||
"429":
|
||||
$ref: "#/components/responses/StringError"
|
||||
"503":
|
||||
$ref: "#/components/responses/StringError"
|
||||
|
||||
/api/orders/{week}:
|
||||
get:
|
||||
operationId: getMyOrder
|
||||
tags: [Orders]
|
||||
summary: Caller's order only
|
||||
security:
|
||||
- portalCognito: []
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WeekPath"
|
||||
responses:
|
||||
"200":
|
||||
description: Empty list when the caller has no order
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/MyOrders"
|
||||
"400":
|
||||
$ref: "#/components/responses/StringError"
|
||||
"403":
|
||||
$ref: "#/components/responses/StringError"
|
||||
"503":
|
||||
$ref: "#/components/responses/StringError"
|
||||
|
||||
/api/admin/orders:
|
||||
get:
|
||||
operationId: adminListOrders
|
||||
tags: [Admin]
|
||||
summary: List weeks or one week's orders
|
||||
security:
|
||||
- portalCognito: []
|
||||
parameters:
|
||||
- name: week
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: Weeks list or week detail
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/AdminOrdersResponse"
|
||||
"403":
|
||||
$ref: "#/components/responses/StringError"
|
||||
put:
|
||||
operationId: adminUpdateOrder
|
||||
tags: [Admin]
|
||||
summary: Recalculate and replace an order
|
||||
security:
|
||||
- portalCognito: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/AdminUpdateBody"
|
||||
responses:
|
||||
"200":
|
||||
description: Updated
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/AdminMutation"
|
||||
"400":
|
||||
$ref: "#/components/responses/StringError"
|
||||
"403":
|
||||
$ref: "#/components/responses/StringError"
|
||||
"404":
|
||||
$ref: "#/components/responses/StringError"
|
||||
"503":
|
||||
$ref: "#/components/responses/StringError"
|
||||
delete:
|
||||
operationId: adminDeleteOrder
|
||||
tags: [Admin]
|
||||
summary: Delete an order
|
||||
security:
|
||||
- portalCognito: []
|
||||
parameters:
|
||||
- name: week
|
||||
in: query
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- name: email
|
||||
in: query
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: Deleted
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/AdminMutation"
|
||||
"400":
|
||||
$ref: "#/components/responses/StringError"
|
||||
"403":
|
||||
$ref: "#/components/responses/StringError"
|
||||
"404":
|
||||
$ref: "#/components/responses/StringError"
|
||||
|
||||
/api/admin/summary-pdf:
|
||||
get:
|
||||
operationId: adminSummaryPdf
|
||||
tags: [Admin]
|
||||
summary: Presigned summary PDF URL
|
||||
security:
|
||||
- portalCognito: []
|
||||
parameters:
|
||||
- name: week
|
||||
in: query
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: Short-lived URL
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/SummaryPdf"
|
||||
"400":
|
||||
$ref: "#/components/responses/StringError"
|
||||
"403":
|
||||
$ref: "#/components/responses/StringError"
|
||||
"404":
|
||||
$ref: "#/components/responses/StringError"
|
||||
"500":
|
||||
$ref: "#/components/responses/StringError"
|
||||
|
||||
/api/roster:
|
||||
get:
|
||||
operationId: getRoster
|
||||
tags: [Orders]
|
||||
summary: Name and email list used by the static form
|
||||
security: []
|
||||
responses:
|
||||
"200":
|
||||
description: Roster
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/FormRoster"
|
||||
|
||||
/api/publish/settings:
|
||||
get:
|
||||
operationId: publishSettings
|
||||
tags: [Publish]
|
||||
summary: Bulk discount and subsidy for scrape
|
||||
security:
|
||||
- publishKey: []
|
||||
responses:
|
||||
"200":
|
||||
description: Pricing fields only
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/PublishSettings"
|
||||
"403":
|
||||
$ref: "#/components/responses/StringError"
|
||||
|
||||
/api/publish/menu:
|
||||
post:
|
||||
operationId: publishMenu
|
||||
tags: [Publish]
|
||||
summary: Write this week's scraped menu
|
||||
security:
|
||||
- publishKey: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/PublishMenuBody"
|
||||
responses:
|
||||
"200":
|
||||
description: Published
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/PublishMenuResult"
|
||||
"400":
|
||||
$ref: "#/components/responses/StringError"
|
||||
"403":
|
||||
$ref: "#/components/responses/StringError"
|
||||
|
||||
components:
|
||||
securitySchemes:
|
||||
portalCognito:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
description: Portal Cognito ID token. GIS google_id_token is also accepted on submit until cutover.
|
||||
publishKey:
|
||||
type: apiKey
|
||||
in: header
|
||||
name: X-Meals-Publish-Key
|
||||
|
||||
parameters:
|
||||
WeekPath:
|
||||
name: week
|
||||
in: path
|
||||
required: true
|
||||
description: "`current`, `YYYY-WNN`, or for orders a calendar date that maps to a week"
|
||||
schema:
|
||||
type: string
|
||||
minLength: 1
|
||||
|
||||
responses:
|
||||
StringError:
|
||||
description: Current meals JSON error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/StringErrorBody"
|
||||
|
||||
schemas:
|
||||
Health:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required: [stage, sha]
|
||||
properties:
|
||||
stage:
|
||||
type: string
|
||||
minLength: 1
|
||||
description: Workspace stage (`dev`, `prod`, or `local`)
|
||||
sha:
|
||||
type: string
|
||||
minLength: 1
|
||||
description: Git SHA or `unknown` locally
|
||||
|
||||
StringErrorBody:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required: [error]
|
||||
properties:
|
||||
error:
|
||||
type: string
|
||||
minLength: 1
|
||||
|
||||
Meal:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
required: [name, price]
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
price:
|
||||
type: number
|
||||
calories:
|
||||
type: [string, number]
|
||||
protein:
|
||||
type: [string, number]
|
||||
description:
|
||||
type: [string, "null"]
|
||||
dietary_tags:
|
||||
type: [array, "null"]
|
||||
items:
|
||||
type: string
|
||||
image_url:
|
||||
type: string
|
||||
is_new:
|
||||
type: boolean
|
||||
|
||||
MenuPayload:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- week
|
||||
- form_status
|
||||
- meal_count
|
||||
- meals
|
||||
- bulk_discount_percent
|
||||
- company_subsidy_percent
|
||||
properties:
|
||||
week:
|
||||
type: string
|
||||
form_status:
|
||||
type: string
|
||||
scraped_at:
|
||||
type: [string, "null"]
|
||||
menu_url:
|
||||
type: string
|
||||
meal_count:
|
||||
type: integer
|
||||
meals:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/Meal"
|
||||
bulk_discount_percent:
|
||||
type: number
|
||||
company_subsidy_percent:
|
||||
type: number
|
||||
order_deadline:
|
||||
type: string
|
||||
|
||||
FormStatus:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required: [week, status]
|
||||
properties:
|
||||
week:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
reopen_at:
|
||||
type: integer
|
||||
|
||||
SubmitItem:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
required: [name, quantity]
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
quantity:
|
||||
type: number
|
||||
retail_price:
|
||||
type: number
|
||||
|
||||
SubmitBody:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
required: [items]
|
||||
properties:
|
||||
items:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/SubmitItem"
|
||||
google_id_token:
|
||||
type: string
|
||||
|
||||
SubmitResult:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required: [status]
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
total:
|
||||
type: number
|
||||
|
||||
MyOrders:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required: [week, orders]
|
||||
properties:
|
||||
week:
|
||||
type: string
|
||||
orders:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required: [employee_email]
|
||||
properties:
|
||||
employee_email:
|
||||
type: string
|
||||
|
||||
AdminWeek:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
required: [week]
|
||||
properties:
|
||||
week:
|
||||
type: string
|
||||
form_status:
|
||||
type: string
|
||||
meal_count:
|
||||
type: integer
|
||||
order_count:
|
||||
type: integer
|
||||
|
||||
AdminOrderItem:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required: [name, quantity, retail_price, price, subtotal]
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
quantity:
|
||||
type: integer
|
||||
retail_price:
|
||||
type: number
|
||||
price:
|
||||
type: number
|
||||
subtotal:
|
||||
type: number
|
||||
|
||||
AdminOrder:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required: [employee_name, employee_email, items, total, submitted_at]
|
||||
properties:
|
||||
employee_name:
|
||||
type: string
|
||||
employee_email:
|
||||
type: string
|
||||
items:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/AdminOrderItem"
|
||||
total:
|
||||
type: number
|
||||
submitted_at:
|
||||
type: string
|
||||
|
||||
AdminWeeks:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required: [weeks]
|
||||
properties:
|
||||
weeks:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/AdminWeek"
|
||||
|
||||
AdminWeekOrders:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required: [week, orders, total_employees, grand_total]
|
||||
properties:
|
||||
week:
|
||||
type: string
|
||||
orders:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/AdminOrder"
|
||||
total_employees:
|
||||
type: integer
|
||||
grand_total:
|
||||
type: number
|
||||
|
||||
AdminOrdersResponse:
|
||||
oneOf:
|
||||
- $ref: "#/components/schemas/AdminWeeks"
|
||||
- $ref: "#/components/schemas/AdminWeekOrders"
|
||||
|
||||
AdminUpdateBody:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required: [week, email, items]
|
||||
properties:
|
||||
week:
|
||||
type: string
|
||||
email:
|
||||
type: string
|
||||
items:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/SubmitItem"
|
||||
|
||||
AdminMutation:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required: [status, week]
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
week:
|
||||
type: string
|
||||
email:
|
||||
type: string
|
||||
total:
|
||||
type: number
|
||||
|
||||
SummaryPdf:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required: [week, url]
|
||||
properties:
|
||||
week:
|
||||
type: string
|
||||
url:
|
||||
type: string
|
||||
format: uri
|
||||
|
||||
FormRoster:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required: [employees]
|
||||
properties:
|
||||
employees:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required: [name, email]
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
email:
|
||||
type: string
|
||||
|
||||
PublishSettings:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required: [bulk_discount_percent, company_subsidy_percent]
|
||||
properties:
|
||||
bulk_discount_percent:
|
||||
type: number
|
||||
company_subsidy_percent:
|
||||
type: number
|
||||
|
||||
PublishMenuBody:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
required: [meals]
|
||||
properties:
|
||||
meals:
|
||||
type: array
|
||||
minItems: 1
|
||||
items:
|
||||
$ref: "#/components/schemas/Meal"
|
||||
scraped_at:
|
||||
type: string
|
||||
menu_url:
|
||||
type: string
|
||||
|
||||
PublishMenuResult:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required: [status, week, meal_count]
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
week:
|
||||
type: string
|
||||
meal_count:
|
||||
type: integer
|
||||
16
package-lock.json
generated
16
package-lock.json
generated
|
|
@ -9,6 +9,7 @@
|
|||
"version": "1.0.0",
|
||||
"devDependencies": {
|
||||
"@eslint/js": "10.0.1",
|
||||
"@redocly/cli": "2.52.1",
|
||||
"eslint": "10.10.0",
|
||||
"globals": "17.12.0",
|
||||
"prettier": "3.9.8"
|
||||
|
|
@ -256,6 +257,21 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@redocly/cli": {
|
||||
"version": "2.52.1",
|
||||
"resolved": "https://registry.npmjs.org/@redocly/cli/-/cli-2.52.1.tgz",
|
||||
"integrity": "sha512-gwfx1WelVDHU/cw+GkpLV9+xksOI3obJGgL1nUVrAFUyXNDxM+aifXDUZk7BfbOIpF69Sea2ant0cOqbQfg/KQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"openapi": "bin/cli.js",
|
||||
"redocly": "bin/cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22.12.0 || >=20.19.0 <21.0.0",
|
||||
"npm": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/esrecurse": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz",
|
||||
|
|
|
|||
|
|
@ -6,10 +6,12 @@
|
|||
"check:templates": "npm run lint:templates && npm run format:templates",
|
||||
"format:templates": "prettier --check \"src/server/templates/*.js\"",
|
||||
"format:templates:write": "prettier --write \"src/server/templates/*.js\"",
|
||||
"lint:templates": "eslint \"src/server/templates/*.js\""
|
||||
"lint:templates": "eslint \"src/server/templates/*.js\"",
|
||||
"openapi:lint": "redocly lint --config .redocly.yaml openapi.yaml"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "10.0.1",
|
||||
"@redocly/cli": "2.52.1",
|
||||
"eslint": "10.10.0",
|
||||
"globals": "17.12.0",
|
||||
"prettier": "3.9.8"
|
||||
|
|
|
|||
21
tests/test_openapi_contract.py
Normal file
21
tests/test_openapi_contract.py
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
"""OpenAPI 3.1 + Redocly recommended, matching internal-portal (DEV-289)."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def test_openapi_uses_redocly_recommended():
|
||||
redocly = (ROOT / ".redocly.yaml").read_text()
|
||||
package = (ROOT / "package.json").read_text()
|
||||
spec = (ROOT / "openapi.yaml").read_text()
|
||||
ci = (ROOT / ".github" / "workflows" / "ci.yml").read_text()
|
||||
assert "extends:" in redocly
|
||||
assert "- recommended" in redocly
|
||||
assert "root: openapi.yaml" in redocly
|
||||
assert '"openapi:lint"' in package
|
||||
assert '"@redocly/cli": "2.52.1"' in package
|
||||
assert "npm run openapi:lint" in ci
|
||||
assert "openapi: 3.1.0" in spec
|
||||
assert "required: [stage, sha]" in spec
|
||||
assert "{ error: string }" in spec or "`{ error: string }`" in spec
|
||||
|
|
@ -33,3 +33,8 @@ def test_meals_owns_a_vpc_instead_of_looking_up_default():
|
|||
assert 'check "existing_subnets_in_vpc"' in data
|
||||
assert "from = aws_vpc.this" in vpc
|
||||
assert "to = aws_vpc.this[0]" in vpc
|
||||
outputs = _read("outputs.tf")
|
||||
assert "output \"vpc_id\"" in outputs
|
||||
assert "value = local.vpc_id" in outputs
|
||||
assert "output \"public_subnet_ids\"" in outputs
|
||||
assert 'check "prod_reuses_afterhours_vpc"' in data
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue