gin-middleware-chain.test.ts 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. import { describe, it, expect, beforeEach, afterEach } from 'vitest';
  2. import * as fs from 'node:fs';
  3. import * as path from 'node:path';
  4. import * as os from 'node:os';
  5. import { CodeGraph } from '../src';
  6. /**
  7. * End-to-end synthesizer test for the gin middleware chain.
  8. *
  9. * `(*Context).Next` runs the handler chain by slice index
  10. * (`c.handlers[c.index](c)`) — a computed dispatch tree-sitter can't resolve, so
  11. * `callees(Next)` would otherwise dead-end at the `len()` helper. Handlers are
  12. * registered via `.Use(...)` / `.GET("/path", h)`. Verify the synthesizer links
  13. * `Next` → each registered NAMED HandlerFunc, captures the wiring site, and
  14. * skips inline (anonymous) closures.
  15. */
  16. describe('gin middleware-chain synthesizer', () => {
  17. let dir: string;
  18. beforeEach(() => {
  19. dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gin-chain-fixture-'));
  20. });
  21. afterEach(() => {
  22. fs.rmSync(dir, { recursive: true, force: true });
  23. });
  24. it('links Context.Next to handlers registered via Use/GET and skips inline closures', async () => {
  25. fs.writeFileSync(path.join(dir, 'go.mod'), 'module ginapp\n\ngo 1.21\n');
  26. // gin-core shape: the dynamic-dispatch chain driver + registration surface.
  27. fs.writeFileSync(
  28. path.join(dir, 'gin.go'),
  29. `package gin
  30. type HandlerFunc func(*Context)
  31. type HandlersChain []HandlerFunc
  32. type Context struct {
  33. handlers HandlersChain
  34. index int8
  35. }
  36. func (c *Context) Next() {
  37. c.index++
  38. for c.index < int8(len(c.handlers)) {
  39. c.handlers[c.index](c)
  40. c.index++
  41. }
  42. }
  43. type Engine struct {
  44. Handlers HandlersChain
  45. }
  46. func (e *Engine) Use(middleware ...HandlerFunc) {
  47. e.Handlers = append(e.Handlers, middleware...)
  48. }
  49. func (e *Engine) GET(path string, handlers ...HandlerFunc) {}
  50. `
  51. );
  52. // registration site: named middleware + named route handler + an inline closure.
  53. fs.writeFileSync(
  54. path.join(dir, 'app.go'),
  55. `package gin
  56. func Logger(c *Context) {}
  57. func Recovery(c *Context) {}
  58. func getUser(c *Context) {}
  59. func setup() {
  60. e := &Engine{}
  61. e.Use(Logger, Recovery)
  62. e.GET("/users", getUser)
  63. e.GET("/inline", func(c *Context) {})
  64. }
  65. `
  66. );
  67. const cg = await CodeGraph.init(dir, { silent: true });
  68. await cg.indexAll();
  69. const db = (cg as any).db.db;
  70. const rows = db
  71. .prepare(
  72. `SELECT s.name source_name, s.kind source_kind, t.name target_name,
  73. json_extract(e.metadata,'$.via') via,
  74. json_extract(e.metadata,'$.registeredAt') registeredAt
  75. FROM edges e
  76. JOIN nodes s ON s.id = e.source
  77. JOIN nodes t ON t.id = e.target
  78. WHERE json_extract(e.metadata,'$.synthesizedBy') = 'gin-middleware-chain'`
  79. )
  80. .all();
  81. cg.close?.();
  82. // Every edge originates from the chain dispatcher Context.Next.
  83. expect(rows.length).toBeGreaterThan(0);
  84. expect(rows.every((r: any) => r.source_name === 'Next' && r.source_kind === 'method')).toBe(true);
  85. // Exactly the three NAMED handlers are linked — the inline closure (4th
  86. // registration) is anonymous and must be skipped.
  87. const targets = new Set(rows.map((r: any) => r.target_name));
  88. expect(targets).toEqual(new Set(['Logger', 'Recovery', 'getUser']));
  89. // The wiring site (`.Use`/`.GET` call) is surfaced for the agent.
  90. const logger = rows.find((r: any) => r.target_name === 'Logger');
  91. expect(logger.via).toBe('Logger');
  92. expect(logger.registeredAt).toMatch(/app\.go:\d+/);
  93. });
  94. });