mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-11-12 08:41:34 +00:00
* fix(group): read net/http HandleFunc and Handle on proven routers as method-agnostic routes (#1668, #1670) The Go provider query only matched the literal `http.HandleFunc(...)` with an identifier or inline handler, so a local `http.NewServeMux()`, a gorilla or chi router, `Handle(...)`, a method-value handler (`api.ListUsers`) and a raw string path produced no provider at all (#1668). It also hard-coded `GET`. A ServeMux registration serves every method unless its pattern names one, so a POST consumer never paired with the route (#1670). The provider is now method-agnostic (`*`), the convention Django, Nest `@All` and Hono `.all` already use and the matcher already pairs with consumers of every verb. A Go 1.22 pattern (`"POST /p"`, `"GET host/p/{id}"`) gives its method and drops the host and the `{$}` anchor, and a gorilla `.Methods(...)` on the route chain gives its verbs. `HandleFunc`/`Handle` are common names outside HTTP (telebot's `b.Handle("/start", fn)`), so the receiver must be a proven router: the net/http package, a binding to `http.NewServeMux()`, gorilla `mux.NewRouter()` or chi `chi.NewRouter()`, a gorilla `PathPrefix("/p").Subrouter()` or chi `Route("/p", func(r chi.Router))` (joining the prefix), or a parameter typed as one of those routers. A router the file mounts under a stripped or prefixed path (`Mount`, `StripPrefix`, gorilla `.Handler`) is declined, as is a struct field, since their registered paths are not their URLs. Routers in `_test.go` files are httptest mocks and are skipped. Handler naming is shared with the gin/echo loop. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(group): decline mounted router parameters and read HEAD routes as GET A router parameter passed to `StripPrefix`/`Mount` was never marked mounted, because the mount scan resolved identifiers through `findBinding`, which returns nothing for a parameter, so its unprefixed paths were emitted. The scan now records a parameter by its declaration and the router proof checks it. A `"HEAD /x"` pattern or gorilla `.Methods("HEAD")` produced an `http::HEAD::` provider, while this plugin's `http.Head` consumers are keyed as GET. Providers now follow the same HEAD-as-GET convention. The plugin docstring no longer says any receiver is accepted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(group): read http.HandlerFunc conversions and hyphenated method tokens `http.Handle("/x", http.HandlerFunc(h))` was dropped because the handler argument is a conversion call; the converted function is now the handler. A Go 1.22 pattern whose method token contains characters other than letters (`"M-SEARCH /discover"`) produced no provider; the method now accepts any HTTP token that starts with a letter. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(group): keep HEAD-only Go routes apart from GET consumers Reading a `"HEAD /x"` pattern or gorilla `.Methods("HEAD")` as GET let every ordinary GET consumer pair with a route that does not serve GET. The provider now stays HEAD. A HEAD consumer, which this plugin records as GET, then misses a HEAD-only route; a missed link is preferred to a false one. Repeated verbs still collapse. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
114 lines
4.5 KiB
TypeScript
114 lines
4.5 KiB
TypeScript
/**
|
|
* End-to-end validation of the INLINE provider source-scan containment path
|
|
* (#2276).
|
|
*
|
|
* The unit suite (`test/unit/group/http-route-extractor.test.ts`) proves the
|
|
* resolver logic by MOCKING `CONTAINING_QUERY` with hand-picked spans. That
|
|
* leaves one assumption unverified: that the REAL ingestion pipeline records a
|
|
* Go enclosing function with a 0-based span that actually contains the emitted
|
|
* call-site line. This test closes that gap.
|
|
*
|
|
* It runs the real pipeline over a Go file whose `http.HandleFunc` handler is an
|
|
* inline `func(){…}` (the issue's headline Go example), persists the resulting
|
|
* graph into a real LadybugDB, and runs the production `HttpRouteExtractor`
|
|
* against the real executor. The provider must resolve to the containing
|
|
* `main()` symbol via line-span containment (`source_scan_resolved`) — not the
|
|
* file-level fallback. Go does not index anonymous func literals as symbols
|
|
* (only `function_declaration`/`method_declaration`), so the innermost
|
|
* containing symbol is `main` itself.
|
|
*/
|
|
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
import fs from 'fs/promises';
|
|
import path from 'path';
|
|
import os from 'os';
|
|
import { runPipelineFromRepo } from '../../src/core/ingestion/pipeline.js';
|
|
import { HttpRouteExtractor } from '../../src/core/group/extractors/http-route-extractor.js';
|
|
import type { CypherExecutor } from '../../src/core/group/contract-extractor.js';
|
|
import type { RepoHandle } from '../../src/core/group/types.js';
|
|
|
|
let tmpBase: string;
|
|
let repoDir: string;
|
|
let storagePath: string;
|
|
let dbPath: string;
|
|
|
|
beforeAll(async () => {
|
|
// Atomic, unique temp dir (fs.mkdtemp) — avoids the predictable
|
|
// os.tmpdir()+name pattern CodeQL flags as an insecure temporary file.
|
|
tmpBase = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-http-inline-e2e-'));
|
|
repoDir = path.join(tmpBase, 'repo');
|
|
storagePath = path.join(tmpBase, '.gitnexus');
|
|
dbPath = path.join(storagePath, 'lbug');
|
|
await fs.mkdir(path.join(repoDir, 'cmd'), { recursive: true });
|
|
await fs.mkdir(dbPath, { recursive: true });
|
|
|
|
// net/http inline handler INSIDE main() — the #2276 Go example. Before this
|
|
// change the func literal was not even captured; now it emits name:null + the
|
|
// call-site line so it resolves to main() by containment.
|
|
await fs.writeFile(
|
|
path.join(repoDir, 'cmd', 'server.go'),
|
|
`package main
|
|
|
|
import "net/http"
|
|
|
|
func main() {
|
|
\thttp.HandleFunc("/api/health", func(w http.ResponseWriter, r *http.Request) {
|
|
\t\tw.Write([]byte("ok"))
|
|
\t})
|
|
\thttp.ListenAndServe(":8080", nil)
|
|
}
|
|
`,
|
|
);
|
|
|
|
const result = await runPipelineFromRepo(repoDir, () => {}, {});
|
|
const adapter = await import('../../src/core/lbug/lbug-adapter.js');
|
|
await adapter.initLbug(dbPath);
|
|
await adapter.loadGraphToLbug(result.graph, repoDir, storagePath);
|
|
}, 120_000);
|
|
|
|
afterAll(async () => {
|
|
try {
|
|
const adapter = await import('../../src/core/lbug/lbug-adapter.js');
|
|
await adapter.closeLbug();
|
|
} catch {
|
|
/* may not have opened */
|
|
}
|
|
if (tmpBase) {
|
|
for (let attempt = 0; attempt < 5; attempt++) {
|
|
try {
|
|
await fs.rm(tmpBase, { recursive: true, force: true });
|
|
return;
|
|
} catch {
|
|
if (attempt < 4) await new Promise((r) => setTimeout(r, 200 * (attempt + 1)));
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
describe('inline Go provider handler resolves via real source-scan containment (#2276)', () => {
|
|
it('resolves an inline http.HandleFunc closure to the containing main() with a real symbolUid', async () => {
|
|
const adapter = await import('../../src/core/lbug/lbug-adapter.js');
|
|
// Param-aware executor (CONTAINING_QUERY binds $filePath) — the same shape
|
|
// production passes to ContractExtractors.
|
|
const dbExecutor: CypherExecutor = (query, params = {}) =>
|
|
adapter.executePrepared(query, params);
|
|
const repo: RepoHandle = {
|
|
id: 'test-repo',
|
|
path: 'repo',
|
|
repoPath: repoDir,
|
|
storagePath,
|
|
};
|
|
|
|
const contracts = await new HttpRouteExtractor().extract(dbExecutor, repoDir, repo);
|
|
const provider = contracts.find(
|
|
(c) => c.role === 'provider' && c.contractId === 'http::*::/api/health',
|
|
);
|
|
|
|
expect(provider).toBeDefined();
|
|
// The real pipeline indexed main() with its true 0-based span; the emitted
|
|
// call-site line lands inside it, so containment yields a real symbolUid
|
|
// rather than the empty file-level fallback.
|
|
expect(provider?.symbolUid).toBeTruthy();
|
|
expect(provider?.symbolName).toBe('main');
|
|
expect(provider?.meta.extractionStrategy).toBe('source_scan_resolved');
|
|
});
|
|
});
|