@terreno/api 0.20.2 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.ai/guidelines/core.md +71 -0
- package/.ai/skills/mongoose-schema-safety/SKILL.md +143 -0
- package/README.md +54 -1
- package/bunfig.toml +1 -1
- package/dist/__tests__/versionCheckPlugin.test.js +29 -7
- package/dist/actions.openApi.test.js +13 -11
- package/dist/api.js +98 -11
- package/dist/api.query.test.js +31 -1
- package/dist/api.test.js +211 -0
- package/dist/auth.test.js +418 -43
- package/dist/betterAuth.d.ts +1 -1
- package/dist/consentApp.test.js +1 -0
- package/dist/example.js +4 -4
- package/dist/expressServer.d.ts +0 -22
- package/dist/expressServer.js +1 -125
- package/dist/expressServer.test.js +90 -91
- package/dist/githubAuth.test.js +22 -22
- package/dist/logger.d.ts +154 -0
- package/dist/logger.js +445 -26
- package/dist/logger.test.js +435 -0
- package/dist/middleware.d.ts +7 -0
- package/dist/middleware.js +58 -1
- package/dist/middleware.test.js +159 -0
- package/dist/models/consentForm.js +2 -1
- package/dist/models/consentResponse.js +2 -1
- package/dist/models/versionConfig.js +2 -1
- package/dist/openApi.test.js +10 -17
- package/dist/openApiBuilder.d.ts +18 -0
- package/dist/openApiBuilder.js +21 -0
- package/dist/openApiBuilder.test.js +34 -10
- package/dist/permissions.test.js +10 -43
- package/dist/populate.test.js +10 -42
- package/dist/realtime/changeStreamWatcher.d.ts +4 -4
- package/dist/realtime/changeStreamWatcher.js +2 -4
- package/dist/realtime/queryMatcher.d.ts +1 -1
- package/dist/realtime/queryMatcher.js +39 -14
- package/dist/realtime/types.d.ts +3 -3
- package/dist/requestContext.d.ts +61 -0
- package/dist/requestContext.js +74 -0
- package/dist/secretProviders.test.js +335 -0
- package/dist/syncConsents.test.js +2 -2
- package/dist/terrenoApp.d.ts +27 -15
- package/dist/terrenoApp.js +24 -14
- package/dist/terrenoApp.test.js +52 -0
- package/dist/tests/bunSetup.js +66 -262
- package/dist/tests/createTestData.d.ts +9 -0
- package/dist/tests/createTestData.js +272 -0
- package/dist/tests/models.d.ts +71 -0
- package/dist/tests/models.js +134 -0
- package/dist/tests/mongoTestSetup.d.ts +7 -0
- package/dist/tests/mongoTestSetup.js +150 -0
- package/dist/tests/testEnv.d.ts +0 -0
- package/dist/tests/testEnv.js +6 -0
- package/dist/tests/testHelper.d.ts +22 -0
- package/dist/tests/testHelper.js +115 -0
- package/dist/tests/types.d.ts +29 -0
- package/dist/tests/types.js +2 -0
- package/dist/tests.d.ts +10 -78
- package/dist/tests.js +24 -241
- package/dist/transformers.test.js +14 -50
- package/package.json +18 -4
- package/src/__snapshots__/openApiBuilder.test.ts.snap +1 -0
- package/src/__tests__/versionCheckPlugin.test.ts +43 -15
- package/src/actions.openApi.test.ts +12 -10
- package/src/api.query.test.ts +24 -1
- package/src/api.test.ts +169 -0
- package/src/api.ts +71 -0
- package/src/auth.test.ts +287 -39
- package/src/betterAuth.ts +1 -1
- package/src/consentApp.test.ts +1 -0
- package/src/example.ts +4 -4
- package/src/expressServer.test.ts +82 -85
- package/src/expressServer.ts +1 -213
- package/src/githubAuth.test.ts +22 -22
- package/src/logger.test.ts +466 -1
- package/src/logger.ts +477 -14
- package/src/middleware.test.ts +74 -2
- package/src/middleware.ts +57 -0
- package/src/models/consentForm.ts +3 -4
- package/src/models/consentResponse.ts +6 -4
- package/src/models/versionConfig.ts +3 -4
- package/src/openApi.test.ts +10 -17
- package/src/openApiBuilder.test.ts +27 -10
- package/src/openApiBuilder.ts +24 -0
- package/src/permissions.test.ts +8 -23
- package/src/populate.test.ts +7 -22
- package/src/realtime/changeStreamWatcher.ts +15 -10
- package/src/realtime/queryMatcher.ts +54 -27
- package/src/realtime/types.ts +4 -4
- package/src/requestContext.ts +86 -0
- package/src/secretProviders.test.ts +219 -1
- package/src/syncConsents.test.ts +1 -1
- package/src/terrenoApp.test.ts +38 -0
- package/src/terrenoApp.ts +37 -15
- package/src/tests/bunSetup.ts +22 -236
- package/src/tests/createTestData.ts +176 -0
- package/src/tests/models.ts +164 -0
- package/src/tests/mongoTestSetup.ts +69 -0
- package/src/tests/testEnv.ts +4 -0
- package/src/tests/testHelper.ts +57 -0
- package/src/tests/types.ts +35 -0
- package/src/tests.ts +40 -231
- package/src/transformers.test.ts +11 -30
- package/tsconfig.typedoc.json +4 -0
- package/dist/tests/index.d.ts +0 -1
- package/dist/tests/index.js +0 -17
- package/src/tests/index.ts +0 -1
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
### @terreno/api
|
|
2
|
+
|
|
3
|
+
REST API framework providing:
|
|
4
|
+
|
|
5
|
+
- **modelRouter**: Auto-generates CRUD endpoints for Mongoose models
|
|
6
|
+
- **Permissions**: `IsAuthenticated`, `IsOwner`, `IsAdmin`, `IsAuthenticatedOrReadOnly`
|
|
7
|
+
- **Query Filters**: `OwnerQueryFilter` for filtering list queries by owner
|
|
8
|
+
- **setupServer**: Express server setup with auth, OpenAPI, and middleware
|
|
9
|
+
- **APIError**: Standardized error handling
|
|
10
|
+
- **logger**: Winston-based logging
|
|
11
|
+
|
|
12
|
+
Key imports:
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
import {
|
|
16
|
+
modelRouter,
|
|
17
|
+
setupServer,
|
|
18
|
+
Permissions,
|
|
19
|
+
OwnerQueryFilter,
|
|
20
|
+
APIError,
|
|
21
|
+
logger,
|
|
22
|
+
asyncHandler,
|
|
23
|
+
authenticateMiddleware,
|
|
24
|
+
} from "@terreno/api";
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
#### modelRouter Usage
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import {modelRouter, modelRouterOptions, Permissions} from "@terreno/api";
|
|
31
|
+
|
|
32
|
+
const router = modelRouter(YourModel, {
|
|
33
|
+
permissions: {
|
|
34
|
+
list: [Permissions.IsAuthenticated],
|
|
35
|
+
create: [Permissions.IsAuthenticated],
|
|
36
|
+
read: [Permissions.IsOwner],
|
|
37
|
+
update: [Permissions.IsOwner],
|
|
38
|
+
delete: [], // Disabled
|
|
39
|
+
},
|
|
40
|
+
sort: "-created",
|
|
41
|
+
queryFields: ["_id", "type", "name"],
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
#### Custom Routes
|
|
46
|
+
|
|
47
|
+
For non-CRUD endpoints, use the OpenAPI builder:
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import {asyncHandler, authenticateMiddleware, createOpenApiBuilder} from "@terreno/api";
|
|
51
|
+
|
|
52
|
+
router.get("/yourRoute/:id", [
|
|
53
|
+
authenticateMiddleware(),
|
|
54
|
+
createOpenApiBuilder(options)
|
|
55
|
+
.withTags(["yourTag"])
|
|
56
|
+
.withSummary("Brief summary")
|
|
57
|
+
.withPathParameter("id", {type: "string"})
|
|
58
|
+
.withResponse(200, {data: {type: "object"}})
|
|
59
|
+
.build(),
|
|
60
|
+
], asyncHandler(async (req, res) => {
|
|
61
|
+
return res.json({data: result});
|
|
62
|
+
}));
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
#### API Conventions
|
|
66
|
+
|
|
67
|
+
- Throw `APIError` with appropriate status codes: `throw new APIError({status: 400, title: "Message"})`
|
|
68
|
+
- Do not use `Model.findOne` — use `Model.findExactlyOne` or `Model.findOneOrThrow`
|
|
69
|
+
- Define statics/methods by direct assignment: `schema.methods = {bar() {}}`
|
|
70
|
+
- All model types live in `src/types/models/`
|
|
71
|
+
- In routes: `req.user` is `UserDocument | undefined`
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: mongoose-schema-safety
|
|
3
|
+
description: >-
|
|
4
|
+
Invoke when making any Mongoose schema change: adding/removing/renaming
|
|
5
|
+
fields, creating a new model, adding indexes, or writing a backfill migration.
|
|
6
|
+
Provides the five-type pattern, risk matrix, type file checklist, and rollout
|
|
7
|
+
safety steps for Terreno backends.
|
|
8
|
+
---
|
|
9
|
+
# Mongoose Schema Safety — Terreno
|
|
10
|
+
|
|
11
|
+
Typical Terreno apps keep models under `backend/src/models/` and types under `backend/src/types/models/`. The Terreno monorepo also uses `example-backend/` and `@terreno/ai` — adjust paths below to match your repo.
|
|
12
|
+
|
|
13
|
+
## Where Schemas and Types Live
|
|
14
|
+
|
|
15
|
+
| Package | Schemas | Types |
|
|
16
|
+
|---------|---------|-------|
|
|
17
|
+
| `@terreno/api` (built-in models like `User`, `ConsentForm`) | `api/src/models/*.ts` | `api/src/types/*.ts` (or co-located in the model file via `mongoose.Schema<Doc, Model, Methods>` generics) |
|
|
18
|
+
| `example-backend` (`User`, `Todo`, `Configuration`) | `example-backend/src/models/*.ts` | `example-backend/src/types/models/*.ts` (`userTypes.ts`, `todoTypes.ts`, …) |
|
|
19
|
+
| `@terreno/ai` (`AIRequest`, `GptHistory`) | `ai/src/models/*.ts` | `ai/src/types/index.ts` |
|
|
20
|
+
|
|
21
|
+
Every field in every schema **must** have a `description` (see the API rule `01-model-field-descriptions.md`). Descriptions flow through to the OpenAPI spec via `mongoose-to-swagger`.
|
|
22
|
+
|
|
23
|
+
## The Five-Type Pattern
|
|
24
|
+
|
|
25
|
+
Every model has manually-maintained TypeScript types. **Types are NOT auto-generated** — every schema change must include a matching type update in the same commit/PR.
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import type {DefaultDoc, DefaultModel, DefaultStatics} from "@terreno/api";
|
|
29
|
+
|
|
30
|
+
// 1. Instance methods (on the document)
|
|
31
|
+
export type YourModelMethods = {
|
|
32
|
+
customMethod: (this: YourModelDocument, param: string) => Promise<void>;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// 2. Static methods (on the model class) — extend DefaultStatics for findExactlyOne / findOneOrNone
|
|
36
|
+
export type YourModelStatics = DefaultStatics<YourModelDocument> & {
|
|
37
|
+
customStatic: (this: YourModelModel, param: string) => Promise<YourModelDocument>;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// 3. Model type combining document + statics
|
|
41
|
+
export type YourModelModel = DefaultModel<YourModelDocument> & YourModelStatics;
|
|
42
|
+
|
|
43
|
+
// 4. Schema type
|
|
44
|
+
export type YourModelSchema = mongoose.Schema<YourModelDocument, YourModelModel, YourModelMethods>;
|
|
45
|
+
|
|
46
|
+
// 5. Document type (the shape of a single record)
|
|
47
|
+
export type YourModelDocument = DefaultDoc & YourModelMethods & {
|
|
48
|
+
fieldName: string;
|
|
49
|
+
optionalField?: number;
|
|
50
|
+
enumField: "value1" | "value2";
|
|
51
|
+
};
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Follow existing type files in `example-backend/src/types/models/` and `ai/src/types/index.ts` for naming and structure.
|
|
55
|
+
|
|
56
|
+
## Statics & Methods — Direct Assignment
|
|
57
|
+
|
|
58
|
+
Define statics and methods by direct assignment on the schema — not via `.method()` / `.static()` / `.add()`:
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
schema.methods = {
|
|
62
|
+
getDisplayName(this: YourModelDocument): string {
|
|
63
|
+
return this.name;
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
schema.statics = {
|
|
67
|
+
async findByEmail(this: YourModelModel, email: string) {
|
|
68
|
+
return this.findOneOrNone({email});
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Critical Mongoose Rules
|
|
74
|
+
|
|
75
|
+
- **Never use `Model.findOne`** — use `Model.findExactlyOne` (throws on 0 or many) or `Model.findOneOrNone` (throws on many). These come from the `findExactlyOne` / `findOneOrNone` plugins in `@terreno/api`.
|
|
76
|
+
- Apply the standard plugins (`createdUpdatedPlugin`, `isDeletedPlugin`, `findOneOrNone`, `findExactlyOne`) on every new model. Most existing models go through `addDefaultPlugins`.
|
|
77
|
+
- `checkModelsStrict()` runs at non-prod startup (`server.ts`) and validates schema consistency — keep it passing.
|
|
78
|
+
|
|
79
|
+
## Schema Change Risk Matrix
|
|
80
|
+
|
|
81
|
+
| Change Type | Risk Level | Required Mitigation |
|
|
82
|
+
|-------------|-----------|---------------------|
|
|
83
|
+
| Add optional field | Low | Safe to ship directly (still requires `description`) |
|
|
84
|
+
| Add required field | High | Must provide a default value, OR write a backfill migration script first |
|
|
85
|
+
| Remove field | Medium | Soft-remove first (mark optional, stop writing); hard-remove in next PR after deploys settle |
|
|
86
|
+
| Rename field | High | Three-step: add new → backfill → remove old (separate PRs) |
|
|
87
|
+
| Change field type | Critical | Treat as rename: new field + migration + remove old |
|
|
88
|
+
| Add index | Medium | Safe in code, but build can slow writes on large collections — coordinate with ops |
|
|
89
|
+
| Remove index | Low | Safe to ship directly |
|
|
90
|
+
| Add unique index | High | Dedup migration must run first; otherwise the index build fails on existing duplicates |
|
|
91
|
+
|
|
92
|
+
## Migration Scripts
|
|
93
|
+
|
|
94
|
+
Migration / backfill scripts live in `example-backend/src/scripts/` (e.g. `syncConsents.ts`, `seedConsents.ts`). They use the `ScriptRunner` type and `BackgroundTask` model from `@terreno/api` (`api/src/scriptRunner.ts`):
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
import type {ScriptContext, ScriptResult, ScriptRunner} from "@terreno/api";
|
|
98
|
+
|
|
99
|
+
export const run: ScriptRunner = async (wetRun, ctx) => {
|
|
100
|
+
const results: string[] = [];
|
|
101
|
+
// ... do work ...
|
|
102
|
+
if (wetRun) {
|
|
103
|
+
// commit changes
|
|
104
|
+
}
|
|
105
|
+
return {success: true, results};
|
|
106
|
+
};
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Always run with `wetRun = false` first to verify the dry-run output. Use `ctx.checkCancellation()`, `ctx.addLog()`, and `ctx.updateProgress()` for long-running tasks.
|
|
110
|
+
|
|
111
|
+
## Cross-Package Ripple
|
|
112
|
+
|
|
113
|
+
A schema change in one place often ripples:
|
|
114
|
+
|
|
115
|
+
- **API surface affected?** Regenerate the SDK with `cd example-frontend && bun run sdk` (or invoke the `generate-sdk` skill). The OpenAPI spec is derived from the schema, so the frontend hooks will go stale otherwise.
|
|
116
|
+
- **`modelRouter` config?** If you added/removed a field, update `queryFields`, `populatePaths`, and `responseHandler` for any router that touches the model.
|
|
117
|
+
- **Admin panel?** If the model is registered in `AdminApp`, update `listFields` so the table reflects the new shape.
|
|
118
|
+
- **Populated refs?** If another model references this one via `ObjectId` + `ref`, and the referenced document shape changed, the consumer's populated type may need updating.
|
|
119
|
+
|
|
120
|
+
## Post-Change Checklist
|
|
121
|
+
|
|
122
|
+
- [ ] All new fields have a `description` (flows to OpenAPI)
|
|
123
|
+
- [ ] Type file updated in the same commit/PR — field names, optionality, enum values, array types, refs, statics/methods all match the schema
|
|
124
|
+
- [ ] For a new model: all five types (`Document`, `Methods`, `Statics`, `Model`, `Schema`) created
|
|
125
|
+
- [ ] Statics/methods assigned directly on schema (`schema.statics = {...}`, `schema.methods = {...}`) — not `.static()` / `.method()`
|
|
126
|
+
- [ ] Other model type files checked — any model that populates this one updated if its shape changed
|
|
127
|
+
- [ ] Migration script written (and dry-run verified with `wetRun = false`) for any backfill
|
|
128
|
+
- [ ] `modelRouter` config updated (`queryFields`, `populatePaths`, `responseHandler`) if needed
|
|
129
|
+
- [ ] `AdminApp` `listFields` updated if the model is in the admin panel
|
|
130
|
+
- [ ] `bun run sdk` run from `example-frontend/` if the API response shape changed
|
|
131
|
+
- [ ] Test covers old-format document behavior (missing new field) if the change rolls out before a backfill
|
|
132
|
+
- [ ] Unique index: dedup migration written and run before the index is added
|
|
133
|
+
- [ ] `checkModelsStrict()` still passes (it runs on non-prod startup)
|
|
134
|
+
- [ ] No `Model.findOne` introduced — use `findExactlyOne` / `findOneOrNone`
|
|
135
|
+
|
|
136
|
+
## Common Pitfalls
|
|
137
|
+
|
|
138
|
+
- Adding a required field without a default — first deploy fails for documents that pre-date the field
|
|
139
|
+
- Skipping the type file update — TS happily compiles older callers but new ones break at runtime
|
|
140
|
+
- Adding a `unique` index without dedup — index build fails on collections with existing duplicates
|
|
141
|
+
- Renaming a field in one PR — frontend gets old field, backend writes new field, neither side is happy
|
|
142
|
+
- Forgetting to regenerate the SDK after a shape change — frontend types drift from reality
|
|
143
|
+
- Using `.method()` / `.static()` API — terreno's convention is direct assignment, and mixing styles makes types hard to maintain
|
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ model instances.
|
|
|
15
15
|
- **Authentication** — JWT with email/password and GitHub OAuth support
|
|
16
16
|
- **Permissions** — Fine-grained access control (IsAuthenticated, IsOwner, IsAdmin, etc.)
|
|
17
17
|
- **OpenAPI** — Automatic spec generation from models and routes
|
|
18
|
-
- **Logging** — Winston-based logging with Google Cloud
|
|
18
|
+
- **Logging** — Winston-based logging with scoped & feature-flagged loggers, automatic request correlation, and Google Cloud / Sentry support
|
|
19
19
|
|
|
20
20
|
## Getting started
|
|
21
21
|
|
|
@@ -165,6 +165,59 @@ setupServer({
|
|
|
165
165
|
|
|
166
166
|
**Learn more:** See the [GitHub OAuth how-to guide](../docs/how-to/add-github-oauth.md) for complete setup instructions.
|
|
167
167
|
|
|
168
|
+
## Logging
|
|
169
|
+
|
|
170
|
+
@terreno/api provides a Winston-based `logger`, two helpers (`createScopedLogger` and
|
|
171
|
+
`createFeatureFlaggedLogger`), and automatic request/job correlation. Always use these instead of
|
|
172
|
+
`console.log` for permanent server logs.
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
import {createFeatureFlaggedLogger, createScopedLogger, logger} from "@terreno/api";
|
|
176
|
+
|
|
177
|
+
// Global logger
|
|
178
|
+
logger.info("Server started", {port: 4000});
|
|
179
|
+
logger.error("Failed to process", {error});
|
|
180
|
+
await chargeCard(id).catch(logger.catch); // logs + captures the exception
|
|
181
|
+
|
|
182
|
+
// Scoped logger: a stable prefix + labels on every line, ideal for multi-step workflows
|
|
183
|
+
const log = createScopedLogger({
|
|
184
|
+
prefix: "[InvoicePay]",
|
|
185
|
+
labels: {invoiceId: invoice._id.toString()},
|
|
186
|
+
});
|
|
187
|
+
log.info("Starting capture"); // -> "[InvoicePay] Starting capture invoiceId=… requestId=…"
|
|
188
|
+
|
|
189
|
+
// Feature-flagged logger: silent until isEnabled() returns true (no redeploy needed)
|
|
190
|
+
const debugLog = createFeatureFlaggedLogger({
|
|
191
|
+
isEnabled: () => process.env.DEBUG_BILLING === "true",
|
|
192
|
+
target: log,
|
|
193
|
+
});
|
|
194
|
+
debugLog.debug("optional detail");
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Correlation
|
|
198
|
+
|
|
199
|
+
While a request or job scope is active, every log line is automatically tagged with `requestId`,
|
|
200
|
+
`userId`, `traceId`, and related fields, so all lines for one request can be grouped together. HTTP
|
|
201
|
+
requests get a scope automatically (the framework also echoes an `X-Request-ID` response header).
|
|
202
|
+
For background jobs, scripts, and cron tasks, open a scope with `runWithRequestContext`:
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
import {createScopedLogger, runWithRequestContext} from "@terreno/api";
|
|
206
|
+
|
|
207
|
+
await runWithRequestContext({jobId: "nightly-sync"}, async () => {
|
|
208
|
+
const log = createScopedLogger({prefix: "[NightlySync]"});
|
|
209
|
+
log.info("started"); // includes jobId + a generated requestId on every line
|
|
210
|
+
await sync();
|
|
211
|
+
});
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
In production, pass a structured transport (e.g. `@google-cloud/logging-winston`) via
|
|
215
|
+
`loggingOptions`/`setupLogging` so the correlation fields and labels reach Log Explorer as
|
|
216
|
+
`jsonPayload`.
|
|
217
|
+
|
|
218
|
+
**Learn more:** See the [Logging & Tracing reference](../docs/reference/api.md#logging--tracing) for
|
|
219
|
+
the full API, label conventions, and Google Cloud Logging / Sentry details.
|
|
220
|
+
|
|
168
221
|
## Sentry
|
|
169
222
|
To enable Sentry, create a "src/sentryInstrumment.ts" file in your project.
|
|
170
223
|
|
package/bunfig.toml
CHANGED
|
@@ -114,7 +114,11 @@ var versionCheckPlugin_1 = require("../versionCheckPlugin");
|
|
|
114
114
|
case 1:
|
|
115
115
|
res = _a.sent();
|
|
116
116
|
(0, bun_test_1.expect)(res.status).toBe(200);
|
|
117
|
-
(0, bun_test_1.expect)(res.body).
|
|
117
|
+
(0, bun_test_1.expect)(res.body.requestId).toBe(res.headers["x-request-id"]);
|
|
118
|
+
(0, bun_test_1.expect)(res.body).toEqual(bun_test_1.expect.objectContaining({
|
|
119
|
+
pollingIntervalMs: 86400000,
|
|
120
|
+
status: "ok",
|
|
121
|
+
}));
|
|
118
122
|
return [2 /*return*/];
|
|
119
123
|
}
|
|
120
124
|
});
|
|
@@ -127,7 +131,8 @@ var versionCheckPlugin_1 = require("../versionCheckPlugin");
|
|
|
127
131
|
case 1:
|
|
128
132
|
res = _a.sent();
|
|
129
133
|
(0, bun_test_1.expect)(res.status).toBe(200);
|
|
130
|
-
(0, bun_test_1.expect)(res.body).
|
|
134
|
+
(0, bun_test_1.expect)(res.body.requestId).toBe(res.headers["x-request-id"]);
|
|
135
|
+
(0, bun_test_1.expect)(res.body).toEqual(bun_test_1.expect.objectContaining({ status: "ok" }));
|
|
131
136
|
return [2 /*return*/];
|
|
132
137
|
}
|
|
133
138
|
});
|
|
@@ -140,7 +145,8 @@ var versionCheckPlugin_1 = require("../versionCheckPlugin");
|
|
|
140
145
|
case 1:
|
|
141
146
|
res = _a.sent();
|
|
142
147
|
(0, bun_test_1.expect)(res.status).toBe(200);
|
|
143
|
-
(0, bun_test_1.expect)(res.body).
|
|
148
|
+
(0, bun_test_1.expect)(res.body.requestId).toBe(res.headers["x-request-id"]);
|
|
149
|
+
(0, bun_test_1.expect)(res.body).toEqual(bun_test_1.expect.objectContaining({ status: "ok" }));
|
|
144
150
|
return [2 /*return*/];
|
|
145
151
|
}
|
|
146
152
|
});
|
|
@@ -159,12 +165,13 @@ var versionCheckPlugin_1 = require("../versionCheckPlugin");
|
|
|
159
165
|
case 2:
|
|
160
166
|
res = _a.sent();
|
|
161
167
|
(0, bun_test_1.expect)(res.status).toBe(200);
|
|
162
|
-
(0, bun_test_1.expect)(res.body).
|
|
168
|
+
(0, bun_test_1.expect)(res.body.requestId).toBe(res.headers["x-request-id"]);
|
|
169
|
+
(0, bun_test_1.expect)(res.body).toEqual(bun_test_1.expect.objectContaining({
|
|
163
170
|
pollingIntervalMs: 86400000,
|
|
164
171
|
requiredVersion: 50,
|
|
165
172
|
status: "ok",
|
|
166
173
|
warningVersion: 100,
|
|
167
|
-
});
|
|
174
|
+
}));
|
|
168
175
|
return [2 /*return*/];
|
|
169
176
|
}
|
|
170
177
|
});
|
|
@@ -184,6 +191,7 @@ var versionCheckPlugin_1 = require("../versionCheckPlugin");
|
|
|
184
191
|
case 2:
|
|
185
192
|
res = _a.sent();
|
|
186
193
|
(0, bun_test_1.expect)(res.status).toBe(200);
|
|
194
|
+
(0, bun_test_1.expect)(res.body.requestId).toBe(res.headers["x-request-id"]);
|
|
187
195
|
(0, bun_test_1.expect)(res.body.status).toBe("warning");
|
|
188
196
|
(0, bun_test_1.expect)(res.body.message).toBe("Please update!");
|
|
189
197
|
return [2 /*return*/];
|
|
@@ -206,6 +214,7 @@ var versionCheckPlugin_1 = require("../versionCheckPlugin");
|
|
|
206
214
|
case 2:
|
|
207
215
|
res = _a.sent();
|
|
208
216
|
(0, bun_test_1.expect)(res.status).toBe(200);
|
|
217
|
+
(0, bun_test_1.expect)(res.body.requestId).toBe(res.headers["x-request-id"]);
|
|
209
218
|
(0, bun_test_1.expect)(res.body.status).toBe("required");
|
|
210
219
|
(0, bun_test_1.expect)(res.body.message).toBe("Update required");
|
|
211
220
|
(0, bun_test_1.expect)(res.body.updateUrl).toBe("https://example.com/update");
|
|
@@ -228,10 +237,12 @@ var versionCheckPlugin_1 = require("../versionCheckPlugin");
|
|
|
228
237
|
return [4 /*yield*/, app.get("/version-check").query({ platform: "web", version: 100 })];
|
|
229
238
|
case 2:
|
|
230
239
|
webRes = _a.sent();
|
|
240
|
+
(0, bun_test_1.expect)(webRes.body.requestId).toBe(webRes.headers["x-request-id"]);
|
|
231
241
|
(0, bun_test_1.expect)(webRes.body.status).toBe("ok");
|
|
232
242
|
return [4 /*yield*/, app.get("/version-check").query({ platform: "mobile", version: 100 })];
|
|
233
243
|
case 3:
|
|
234
244
|
mobileRes = _a.sent();
|
|
245
|
+
(0, bun_test_1.expect)(mobileRes.body.requestId).toBe(mobileRes.headers["x-request-id"]);
|
|
235
246
|
(0, bun_test_1.expect)(mobileRes.body.status).toBe("required");
|
|
236
247
|
return [2 /*return*/];
|
|
237
248
|
}
|
|
@@ -250,6 +261,7 @@ var versionCheckPlugin_1 = require("../versionCheckPlugin");
|
|
|
250
261
|
return [4 /*yield*/, app.get("/version-check").query({ platform: "invalid", version: 50 })];
|
|
251
262
|
case 2:
|
|
252
263
|
res = _a.sent();
|
|
264
|
+
(0, bun_test_1.expect)(res.body.requestId).toBe(res.headers["x-request-id"]);
|
|
253
265
|
(0, bun_test_1.expect)(res.body.status).toBe("required");
|
|
254
266
|
return [2 /*return*/];
|
|
255
267
|
}
|
|
@@ -269,12 +281,13 @@ var versionCheckPlugin_1 = require("../versionCheckPlugin");
|
|
|
269
281
|
case 2:
|
|
270
282
|
res = _a.sent();
|
|
271
283
|
(0, bun_test_1.expect)(res.status).toBe(200);
|
|
272
|
-
(0, bun_test_1.expect)(res.body).
|
|
284
|
+
(0, bun_test_1.expect)(res.body.requestId).toBe(res.headers["x-request-id"]);
|
|
285
|
+
(0, bun_test_1.expect)(res.body).toEqual(bun_test_1.expect.objectContaining({
|
|
273
286
|
pollingIntervalMs: 86400000,
|
|
274
287
|
requiredVersion: 50,
|
|
275
288
|
status: "ok",
|
|
276
289
|
warningVersion: 100,
|
|
277
|
-
});
|
|
290
|
+
}));
|
|
278
291
|
return [2 /*return*/];
|
|
279
292
|
}
|
|
280
293
|
});
|
|
@@ -294,6 +307,7 @@ var versionCheckPlugin_1 = require("../versionCheckPlugin");
|
|
|
294
307
|
case 2:
|
|
295
308
|
res = _a.sent();
|
|
296
309
|
(0, bun_test_1.expect)(res.status).toBe(200);
|
|
310
|
+
(0, bun_test_1.expect)(res.body.requestId).toBe(res.headers["x-request-id"]);
|
|
297
311
|
(0, bun_test_1.expect)(res.body.pollingIntervalMs).toBe(3600000);
|
|
298
312
|
return [2 /*return*/];
|
|
299
313
|
}
|
|
@@ -313,6 +327,7 @@ var versionCheckPlugin_1 = require("../versionCheckPlugin");
|
|
|
313
327
|
case 2:
|
|
314
328
|
res = _a.sent();
|
|
315
329
|
(0, bun_test_1.expect)(res.status).toBe(200);
|
|
330
|
+
(0, bun_test_1.expect)(res.body.requestId).toBe(res.headers["x-request-id"]);
|
|
316
331
|
(0, bun_test_1.expect)(res.body.pollingIntervalMs).toBe(86400000);
|
|
317
332
|
return [2 /*return*/];
|
|
318
333
|
}
|
|
@@ -332,6 +347,7 @@ var versionCheckPlugin_1 = require("../versionCheckPlugin");
|
|
|
332
347
|
case 2:
|
|
333
348
|
res = _a.sent();
|
|
334
349
|
(0, bun_test_1.expect)(res.status).toBe(200);
|
|
350
|
+
(0, bun_test_1.expect)(res.body.requestId).toBe(res.headers["x-request-id"]);
|
|
335
351
|
(0, bun_test_1.expect)(res.body.status).toBe("required");
|
|
336
352
|
return [2 /*return*/];
|
|
337
353
|
}
|
|
@@ -351,6 +367,7 @@ var versionCheckPlugin_1 = require("../versionCheckPlugin");
|
|
|
351
367
|
case 2:
|
|
352
368
|
res = _a.sent();
|
|
353
369
|
(0, bun_test_1.expect)(res.status).toBe(200);
|
|
370
|
+
(0, bun_test_1.expect)(res.body.requestId).toBe(res.headers["x-request-id"]);
|
|
354
371
|
(0, bun_test_1.expect)(res.body.status).toBe("warning");
|
|
355
372
|
(0, bun_test_1.expect)(res.body.message).toBe("A new version is available. Please update for the best experience.");
|
|
356
373
|
return [2 /*return*/];
|
|
@@ -371,6 +388,7 @@ var versionCheckPlugin_1 = require("../versionCheckPlugin");
|
|
|
371
388
|
case 2:
|
|
372
389
|
res = _a.sent();
|
|
373
390
|
(0, bun_test_1.expect)(res.status).toBe(200);
|
|
391
|
+
(0, bun_test_1.expect)(res.body.requestId).toBe(res.headers["x-request-id"]);
|
|
374
392
|
(0, bun_test_1.expect)(res.body.status).toBe("required");
|
|
375
393
|
(0, bun_test_1.expect)(res.body.message).toBe("This version is no longer supported. Please update to continue.");
|
|
376
394
|
return [2 /*return*/];
|
|
@@ -391,6 +409,7 @@ var versionCheckPlugin_1 = require("../versionCheckPlugin");
|
|
|
391
409
|
case 2:
|
|
392
410
|
res = _a.sent();
|
|
393
411
|
(0, bun_test_1.expect)(res.status).toBe(200);
|
|
412
|
+
(0, bun_test_1.expect)(res.body.requestId).toBe(res.headers["x-request-id"]);
|
|
394
413
|
(0, bun_test_1.expect)(res.body.status).toBe("warning");
|
|
395
414
|
return [2 /*return*/];
|
|
396
415
|
}
|
|
@@ -409,11 +428,13 @@ var versionCheckPlugin_1 = require("../versionCheckPlugin");
|
|
|
409
428
|
return [4 /*yield*/, app.get("/version-check").query({ platform: "web", version: 150 })];
|
|
410
429
|
case 2:
|
|
411
430
|
warningRes = _a.sent();
|
|
431
|
+
(0, bun_test_1.expect)(warningRes.body.requestId).toBe(warningRes.headers["x-request-id"]);
|
|
412
432
|
(0, bun_test_1.expect)(warningRes.body.status).toBe("warning");
|
|
413
433
|
(0, bun_test_1.expect)(warningRes.body.message).toBe("A new version is available. Please update for the best experience.");
|
|
414
434
|
return [4 /*yield*/, app.get("/version-check").query({ platform: "web", version: 50 })];
|
|
415
435
|
case 3:
|
|
416
436
|
requiredRes = _a.sent();
|
|
437
|
+
(0, bun_test_1.expect)(requiredRes.body.requestId).toBe(requiredRes.headers["x-request-id"]);
|
|
417
438
|
(0, bun_test_1.expect)(requiredRes.body.status).toBe("required");
|
|
418
439
|
(0, bun_test_1.expect)(requiredRes.body.message).toBe("This version is no longer supported. Please update to continue.");
|
|
419
440
|
return [2 /*return*/];
|
|
@@ -428,6 +449,7 @@ var versionCheckPlugin_1 = require("../versionCheckPlugin");
|
|
|
428
449
|
case 1:
|
|
429
450
|
res = _a.sent();
|
|
430
451
|
(0, bun_test_1.expect)(res.status).toBe(200);
|
|
452
|
+
(0, bun_test_1.expect)(res.body.requestId).toBe(res.headers["x-request-id"]);
|
|
431
453
|
return [2 /*return*/];
|
|
432
454
|
}
|
|
433
455
|
});
|
|
@@ -70,8 +70,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
70
70
|
var bun_test_1 = require("bun:test");
|
|
71
71
|
var supertest_1 = __importDefault(require("supertest"));
|
|
72
72
|
var api_1 = require("./api");
|
|
73
|
-
var auth_1 = require("./auth");
|
|
74
|
-
var expressServer_1 = require("./expressServer");
|
|
75
73
|
var permissions_1 = require("./permissions");
|
|
76
74
|
var terrenoApp_1 = require("./terrenoApp");
|
|
77
75
|
var tests_1 = require("./tests");
|
|
@@ -137,6 +135,7 @@ var primeActionOpenApiRoutes = function (server, foodId) { return __awaiter(void
|
|
|
137
135
|
}); };
|
|
138
136
|
var assertActionOpenApiSpec = function (spec) {
|
|
139
137
|
var _a, _b, _c, _d, _e;
|
|
138
|
+
(0, bun_test_1.expect)(spec.requestId).toBeUndefined();
|
|
140
139
|
var paths = spec.paths;
|
|
141
140
|
var collectionPath = paths["/food/summarize"];
|
|
142
141
|
var instancePath = paths["/food/{id}/ping"];
|
|
@@ -193,7 +192,7 @@ var assertActionOpenApiSpec = function (spec) {
|
|
|
193
192
|
}); });
|
|
194
193
|
(0, bun_test_1.describe)("TerrenoApp", function () {
|
|
195
194
|
(0, bun_test_1.it)("includes action operations in openapi.json after first request", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
196
|
-
var foodRegistration, app, server, specRes;
|
|
195
|
+
var foodRegistration, app, server, specRes, pingRes;
|
|
197
196
|
return __generator(this, function (_a) {
|
|
198
197
|
switch (_a.label) {
|
|
199
198
|
case 0:
|
|
@@ -212,26 +211,29 @@ var assertActionOpenApiSpec = function (spec) {
|
|
|
212
211
|
case 2:
|
|
213
212
|
specRes = _a.sent();
|
|
214
213
|
assertActionOpenApiSpec(specRes.body);
|
|
214
|
+
return [4 /*yield*/, server.get("/food/".concat(foodId, "/ping")).expect(200)];
|
|
215
|
+
case 3:
|
|
216
|
+
pingRes = _a.sent();
|
|
217
|
+
(0, bun_test_1.expect)(pingRes.body.data).toEqual({ id: foodId });
|
|
218
|
+
(0, bun_test_1.expect)(pingRes.body.requestId).toBe(pingRes.headers["x-request-id"]);
|
|
215
219
|
return [2 /*return*/];
|
|
216
220
|
}
|
|
217
221
|
});
|
|
218
222
|
}); });
|
|
219
223
|
});
|
|
220
|
-
(0, bun_test_1.describe)("
|
|
224
|
+
(0, bun_test_1.describe)("configureApp", function () {
|
|
221
225
|
var app;
|
|
222
226
|
(0, bun_test_1.beforeEach)(function () {
|
|
223
|
-
var
|
|
227
|
+
var configureApp = function (router, routerOptions) {
|
|
224
228
|
router.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, __assign(__assign({}, foodActionRouterOptions), routerOptions)));
|
|
225
229
|
};
|
|
226
|
-
app =
|
|
227
|
-
|
|
230
|
+
app = new terrenoApp_1.TerrenoApp({
|
|
231
|
+
configureApp: configureApp,
|
|
228
232
|
skipListen: true,
|
|
229
233
|
userModel: tests_1.UserModel,
|
|
230
|
-
});
|
|
231
|
-
(0, auth_1.setupAuth)(app, tests_1.UserModel);
|
|
232
|
-
(0, auth_1.addAuthRoutes)(app, tests_1.UserModel);
|
|
234
|
+
}).build();
|
|
233
235
|
});
|
|
234
|
-
(0, bun_test_1.it)("emits the same action operations on first hit via
|
|
236
|
+
(0, bun_test_1.it)("emits the same action operations on first hit via configureApp", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
235
237
|
var server, specRes;
|
|
236
238
|
return __generator(this, function (_a) {
|
|
237
239
|
switch (_a.label) {
|