114 lines
3.4 KiB
TypeScript
114 lines
3.4 KiB
TypeScript
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+/);
|
|
});
|
|
});
|