| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113 |
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
- import * as fs from 'node:fs';
- import * as path from 'node:path';
- import * as os from 'node:os';
- import { CodeGraph } from '../src';
- /**
- * End-to-end synthesizer test for the gin middleware chain.
- *
- * `(*Context).Next` runs the handler chain by slice index
- * (`c.handlers[c.index](c)`) — a computed dispatch tree-sitter can't resolve, so
- * `callees(Next)` would otherwise dead-end at the `len()` helper. Handlers are
- * registered via `.Use(...)` / `.GET("/path", h)`. Verify the synthesizer links
- * `Next` → each registered NAMED HandlerFunc, captures the wiring site, and
- * skips inline (anonymous) closures.
- */
- describe('gin middleware-chain synthesizer', () => {
- let dir: string;
- beforeEach(() => {
- dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gin-chain-fixture-'));
- });
- afterEach(() => {
- fs.rmSync(dir, { recursive: true, force: true });
- });
- it('links Context.Next to handlers registered via Use/GET and skips inline closures', async () => {
- fs.writeFileSync(path.join(dir, 'go.mod'), 'module ginapp\n\ngo 1.21\n');
- // gin-core shape: the dynamic-dispatch chain driver + registration surface.
- fs.writeFileSync(
- path.join(dir, 'gin.go'),
- `package gin
- type HandlerFunc func(*Context)
- type HandlersChain []HandlerFunc
- type Context struct {
- handlers HandlersChain
- index int8
- }
- func (c *Context) Next() {
- c.index++
- for c.index < int8(len(c.handlers)) {
- c.handlers[c.index](c)
- c.index++
- }
- }
- type Engine struct {
- Handlers HandlersChain
- }
- func (e *Engine) Use(middleware ...HandlerFunc) {
- e.Handlers = append(e.Handlers, middleware...)
- }
- func (e *Engine) GET(path string, handlers ...HandlerFunc) {}
- `
- );
- // registration site: named middleware + named route handler + an inline closure.
- fs.writeFileSync(
- path.join(dir, 'app.go'),
- `package gin
- func Logger(c *Context) {}
- func Recovery(c *Context) {}
- func getUser(c *Context) {}
- func setup() {
- e := &Engine{}
- e.Use(Logger, Recovery)
- e.GET("/users", getUser)
- e.GET("/inline", func(c *Context) {})
- }
- `
- );
- const cg = await CodeGraph.init(dir, { silent: true });
- await cg.indexAll();
- const db = (cg as any).db.db;
- const rows = db
- .prepare(
- `SELECT s.name source_name, s.kind source_kind, t.name target_name,
- json_extract(e.metadata,'$.via') via,
- json_extract(e.metadata,'$.registeredAt') registeredAt
- FROM edges e
- JOIN nodes s ON s.id = e.source
- JOIN nodes t ON t.id = e.target
- WHERE json_extract(e.metadata,'$.synthesizedBy') = 'gin-middleware-chain'`
- )
- .all();
- cg.close?.();
- // Every edge originates from the chain dispatcher Context.Next.
- expect(rows.length).toBeGreaterThan(0);
- expect(rows.every((r: any) => r.source_name === 'Next' && r.source_kind === 'method')).toBe(true);
- // Exactly the three NAMED handlers are linked — the inline closure (4th
- // registration) is anonymous and must be skipped.
- const targets = new Set(rows.map((r: any) => r.target_name));
- expect(targets).toEqual(new Set(['Logger', 'Recovery', 'getUser']));
- // The wiring site (`.Use`/`.GET` call) is surfaced for the agent.
- const logger = rows.find((r: any) => r.target_name === 'Logger');
- expect(logger.via).toBe('Logger');
- expect(logger.registeredAt).toMatch(/app\.go:\d+/);
- });
- });
|