package contracts import ( "testing" ) // TestExtractFirstCallArg covers the call-site-parsing contract every // other piece of wrapper inlining relies on. If this goes wrong then // InlineWrappers silently skips real callers or emits nonsense // contracts — both failure modes are invisible in end-to-end tests // until they corrupt match counts, so it's worth exhaustively // specifying the allowed inputs here. func TestExtractFirstCallArg(t *testing.T) { tests := []struct { name string src string line int wrapper string lang string wantKind argKind wantValue string wantMethod string }{ { name: "string literal", src: "function f() {\n return request('/v1/users', getToken);\n}\n", line: 2, wrapper: "request", lang: "typescript", wantKind: argLiteral, wantValue: "/v1/users", }, { name: "template literal with param", src: "function f() {\n return request(`/v1/users/${id}`, getToken);\n}\n", line: 2, wrapper: "request", lang: "typescript", wantKind: argLiteral, wantValue: "/v1/users/${id}", }, { name: "string literal + object method", src: "function f() {\n return request('/v1/tucks', getToken, {method: 'POST', body: JSON.stringify(d)});\n}\n", line: 2, wrapper: "request", lang: "typescript", wantKind: argLiteral, wantValue: "/v1/tucks", wantMethod: "POST", }, { name: "double-quoted method with backticks in wrapper", src: "function f() {\n return request(`/v1/tucks/${id}`, getToken, { method: \"PATCH\" });\n}\n", line: 2, wrapper: "request", lang: "typescript", wantKind: argLiteral, wantValue: "/v1/tucks/${id}", wantMethod: "PATCH", }, { name: "bare parameter identifier", src: "function doFetch(path, token) {\n return fetch(path, opts);\n}\n", line: 2, wrapper: "fetch", lang: "javascript", wantKind: argBareParam, }, { name: "pure interpolation template — wrapper forwarding", src: "function f(url) {\n return request(`${url}`, getToken);\n}\n", line: 2, wrapper: "request", lang: "typescript", wantKind: argBareParam, }, { name: "generic type params on TS wrapper", src: "function f() {\n return request('/v1/me', getToken);\n}\n", line: 2, wrapper: "request", lang: "typescript", wantKind: argLiteral, wantValue: "/v1/me", }, { name: "runtime expression — not a literal, not a bare ident", src: "function f() {\n return request(buildPath() + '/v1/x', getToken);\n}\n", line: 2, wrapper: "request", lang: "typescript", wantKind: argUnknown, }, { name: "go-ish call with nested map options", src: "func f() {\n\treturn request(\"/v1/tucks\", token, map[string]any{\"method\": \"DELETE\"})\n}\n", line: 2, wrapper: "request", lang: "go", wantKind: argLiteral, wantValue: "/v1/tucks", wantMethod: "DELETE", }, { name: "multi-line call spanning across object-literal body", src: "function f() {\n return request('/v1/tucks', getToken, {\n method: 'POST',\n body: JSON.stringify(data),\n });\n}\n", line: 2, wrapper: "request", lang: "typescript", wantKind: argLiteral, wantValue: "/v1/tucks", wantMethod: "POST", }, { name: "wrapper name not on the target line", src: "function f() {\n const x = 1;\n return request('/v1/users', getToken);\n}\n", line: 2, wrapper: "request", lang: "typescript", wantKind: argUnknown, }, { name: "different function name on the line", src: "function f() {\n return callApi('/v1/users', getToken);\n}\n", line: 2, wrapper: "request", lang: "typescript", wantKind: argUnknown, }, { name: "word-boundary — xrequest should not match request", src: "function f() {\n return xrequest('/v1/users', getToken);\n}\n", line: 2, wrapper: "request", lang: "typescript", wantKind: argUnknown, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := extractFirstCallArg([]byte(tt.src), tt.line, tt.wrapper, tt.lang) if got.Kind != tt.wantKind { t.Errorf("Kind: want %v, got %v", tt.wantKind, got.Kind) } if tt.wantKind == argLiteral && got.Value != tt.wantValue { t.Errorf("Value: want %q, got %q", tt.wantValue, got.Value) } if got.Method != tt.wantMethod { t.Errorf("Method: want %q, got %q", tt.wantMethod, got.Method) } }) } } func TestIsWrapperPath(t *testing.T) { tests := []struct { path string want bool }{ {"/{path}", true}, {"/{url}", true}, {"/{endpoint}", true}, {"/path", true}, {"/url", true}, {"/v1/tucks", false}, {"/v1/tucks/{id}", false}, {"/", false}, {"", false}, {"/{id}/suffix", false}, } for _, tt := range tests { if got := isWrapperPath(tt.path); got != tt.want { t.Errorf("isWrapperPath(%q) = %v, want %v", tt.path, got, tt.want) } } }