|
1 | | -import { describe, it, expect, mock } from "bun:test"; |
2 | | -import { createApp } from "../app"; |
3 | | -import { z } from "zod/v4"; |
4 | | -import { createTestAppClient } from "../testing"; |
| 1 | +import { describe, expect, it, mock } from "bun:test"; |
5 | 2 | import { expectTypeOf } from "expect-type"; |
6 | | -import type { AnyDef, GetAppData } from "../types"; |
| 3 | +import type { OpenAPI } from "openapi-types"; |
| 4 | +import { z } from "zod/v4"; |
7 | 5 | import { zodSchemaAdapter } from "../adapters/zod-schema-adapter"; |
| 6 | +import { createApp } from "../app"; |
8 | 7 | import { HttpStatus } from "../status"; |
| 8 | +import { createTestAppClient } from "../testing"; |
| 9 | +import type { AnyDef, GetAppData } from "../types"; |
9 | 10 |
|
10 | 11 | // Silence console.error logs |
11 | 12 | globalThis.console.error = mock(); |
@@ -448,4 +449,119 @@ describe("App", () => { |
448 | 449 | expect(actual).toMatchObject({ a: "A" }); |
449 | 450 | }); |
450 | 451 | }); |
| 452 | + |
| 453 | + describe("app-level OpenAPI options", () => { |
| 454 | + describe("tags", () => { |
| 455 | + it("should apply app-level tags to all routes", () => { |
| 456 | + const app = createApp({ |
| 457 | + schemaAdapter: zodSchemaAdapter, |
| 458 | + tags: ["Users"], |
| 459 | + }) |
| 460 | + .get("/", { responses: z.string() }, () => "") |
| 461 | + .post("/", { responses: z.string() }, () => ""); |
| 462 | + |
| 463 | + const spec = app.getOpenApiSpec() as OpenAPI.Document; |
| 464 | + |
| 465 | + expect((spec.paths!["/"] as any).get.tags).toEqual(["Users"]); |
| 466 | + expect((spec.paths!["/"] as any).post.tags).toEqual(["Users"]); |
| 467 | + }); |
| 468 | + |
| 469 | + it("should allow route-level tags to override app-level tags", () => { |
| 470 | + const app = createApp({ |
| 471 | + schemaAdapter: zodSchemaAdapter, |
| 472 | + tags: ["Users"], |
| 473 | + }).get("/", { tags: ["Admin"], responses: z.string() }, () => ""); |
| 474 | + |
| 475 | + const spec = app.getOpenApiSpec() as OpenAPI.Document; |
| 476 | + |
| 477 | + expect((spec.paths!["/"] as any).get.tags).toEqual(["Admin"]); |
| 478 | + }); |
| 479 | + |
| 480 | + it("should preserve app-level tags when nested via use()", () => { |
| 481 | + const usersApp = createApp({ |
| 482 | + prefix: "/users", |
| 483 | + schemaAdapter: zodSchemaAdapter, |
| 484 | + tags: ["Users"], |
| 485 | + }).get("/", { responses: z.string() }, () => ""); |
| 486 | + |
| 487 | + const app = createApp({ schemaAdapter: zodSchemaAdapter }).use( |
| 488 | + usersApp, |
| 489 | + ); |
| 490 | + |
| 491 | + const spec = app.getOpenApiSpec() as OpenAPI.Document; |
| 492 | + |
| 493 | + expect((spec.paths!["/users"] as any).get.tags).toEqual(["Users"]); |
| 494 | + }); |
| 495 | + }); |
| 496 | + |
| 497 | + describe("security", () => { |
| 498 | + it("should apply app-level security to all routes", () => { |
| 499 | + const app = createApp({ |
| 500 | + schemaAdapter: zodSchemaAdapter, |
| 501 | + security: [{ bearerAuth: [] }], |
| 502 | + }) |
| 503 | + .get("/", { responses: z.string() }, () => "") |
| 504 | + .post("/", { responses: z.string() }, () => ""); |
| 505 | + |
| 506 | + const spec = app.getOpenApiSpec() as OpenAPI.Document; |
| 507 | + |
| 508 | + expect((spec.paths!["/"] as any).get.security).toEqual([ |
| 509 | + { bearerAuth: [] }, |
| 510 | + ]); |
| 511 | + expect((spec.paths!["/"] as any).post.security).toEqual([ |
| 512 | + { bearerAuth: [] }, |
| 513 | + ]); |
| 514 | + }); |
| 515 | + |
| 516 | + it("should allow route-level security to override app-level security", () => { |
| 517 | + const app = createApp({ |
| 518 | + schemaAdapter: zodSchemaAdapter, |
| 519 | + security: [{ bearerAuth: [] }], |
| 520 | + }).get( |
| 521 | + "/admin", |
| 522 | + { security: [{ adminKey: [] }], responses: z.string() }, |
| 523 | + () => "", |
| 524 | + ); |
| 525 | + |
| 526 | + const spec = app.getOpenApiSpec() as OpenAPI.Document; |
| 527 | + |
| 528 | + expect((spec.paths!["/admin"] as any).get.security).toEqual([ |
| 529 | + { adminKey: [] }, |
| 530 | + ]); |
| 531 | + }); |
| 532 | + |
| 533 | + it("should preserve app-level security when nested via use()", () => { |
| 534 | + const authApp = createApp({ |
| 535 | + prefix: "/auth", |
| 536 | + schemaAdapter: zodSchemaAdapter, |
| 537 | + security: [{ bearerAuth: [] }], |
| 538 | + }).get("/profile", { responses: z.string() }, () => ""); |
| 539 | + |
| 540 | + const app = createApp({ schemaAdapter: zodSchemaAdapter }).use(authApp); |
| 541 | + |
| 542 | + const spec = app.getOpenApiSpec() as OpenAPI.Document; |
| 543 | + |
| 544 | + expect((spec.paths!["/auth/profile"] as any).get.security).toEqual([ |
| 545 | + { bearerAuth: [] }, |
| 546 | + ]); |
| 547 | + }); |
| 548 | + }); |
| 549 | + |
| 550 | + describe("tags and security combined", () => { |
| 551 | + it("should apply both tags and security to routes", () => { |
| 552 | + const app = createApp({ |
| 553 | + schemaAdapter: zodSchemaAdapter, |
| 554 | + tags: ["Auth"], |
| 555 | + security: [{ bearerAuth: [] }], |
| 556 | + }).get("/profile", { responses: z.string() }, () => ""); |
| 557 | + |
| 558 | + const spec = app.getOpenApiSpec() as OpenAPI.Document; |
| 559 | + |
| 560 | + expect((spec.paths!["/profile"] as any).get.tags).toEqual(["Auth"]); |
| 561 | + expect((spec.paths!["/profile"] as any).get.security).toEqual([ |
| 562 | + { bearerAuth: [] }, |
| 563 | + ]); |
| 564 | + }); |
| 565 | + }); |
| 566 | + }); |
451 | 567 | }); |
0 commit comments