Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

391 lines
12 KiB
Go

package contracts
import "testing"
func TestMatch_ProviderConsumerPairing(t *testing.T) {
reg := NewRegistry()
// Two repos, ONE workspace — the model for legitimate cross-repo
// pairing (microservices behind a shared gateway). Both contracts
// declare WorkspaceID="acme" so the matcher's boundary check pairs
// them as a CrossRepo link. Without a shared workspace the
// boundary would (correctly) treat them as orphans.
reg.Add(Contract{
ID: "http::GET::/api/users",
Type: ContractHTTP,
Role: RoleProvider,
SymbolID: "svc-a::listUsers",
FilePath: "routes.go",
RepoPrefix: "svc-a",
WorkspaceID: "acme",
ProjectID: "users",
Confidence: 0.9,
})
reg.Add(Contract{
ID: "http::GET::/api/users",
Type: ContractHTTP,
Role: RoleConsumer,
SymbolID: "svc-b::fetchUsers",
FilePath: "client.go",
RepoPrefix: "svc-b",
WorkspaceID: "acme",
ProjectID: "users",
Confidence: 0.9,
})
result := Match(reg)
if len(result.Matched) != 1 {
t.Fatalf("expected 1 match, got %d", len(result.Matched))
}
m := result.Matched[0]
if m.ContractID != "http::GET::/api/users" {
t.Errorf("wrong contract ID: %s", m.ContractID)
}
if m.Provider.SymbolID != "svc-a::listUsers" {
t.Errorf("wrong provider: %s", m.Provider.SymbolID)
}
if m.Consumer.SymbolID != "svc-b::fetchUsers" {
t.Errorf("wrong consumer: %s", m.Consumer.SymbolID)
}
if !m.CrossRepo {
t.Error("expected cross-repo match")
}
if len(result.OrphanProviders) != 0 {
t.Errorf("expected 0 orphan providers, got %d", len(result.OrphanProviders))
}
if len(result.OrphanConsumers) != 0 {
t.Errorf("expected 0 orphan consumers, got %d", len(result.OrphanConsumers))
}
}
func TestMatch_OrphanProvider(t *testing.T) {
reg := NewRegistry()
reg.Add(Contract{
ID: "http::POST::/api/orders",
Type: ContractHTTP,
Role: RoleProvider,
SymbolID: "svc-a::createOrder",
FilePath: "routes.go",
RepoPrefix: "svc-a",
})
result := Match(reg)
if len(result.Matched) != 0 {
t.Errorf("expected 0 matches, got %d", len(result.Matched))
}
if len(result.OrphanProviders) != 1 {
t.Fatalf("expected 1 orphan provider, got %d", len(result.OrphanProviders))
}
if result.OrphanProviders[0].SymbolID != "svc-a::createOrder" {
t.Errorf("wrong orphan provider: %s", result.OrphanProviders[0].SymbolID)
}
}
func TestMatch_OrphanConsumer(t *testing.T) {
reg := NewRegistry()
reg.Add(Contract{
ID: "http::GET::/api/missing",
Type: ContractHTTP,
Role: RoleConsumer,
SymbolID: "svc-b::callMissing",
FilePath: "client.go",
RepoPrefix: "svc-b",
})
result := Match(reg)
if len(result.Matched) != 0 {
t.Errorf("expected 0 matches, got %d", len(result.Matched))
}
if len(result.OrphanConsumers) != 1 {
t.Fatalf("expected 1 orphan consumer, got %d", len(result.OrphanConsumers))
}
}
func TestMatch_SameRepoNotCrossRepo(t *testing.T) {
reg := NewRegistry()
reg.Add(Contract{
ID: "http::GET::/api/health",
Type: ContractHTTP,
Role: RoleProvider,
SymbolID: "svc::healthHandler",
FilePath: "health.go",
RepoPrefix: "svc",
})
reg.Add(Contract{
ID: "http::GET::/api/health",
Type: ContractHTTP,
Role: RoleConsumer,
SymbolID: "svc::selfCheck",
FilePath: "check.go",
RepoPrefix: "svc",
})
result := Match(reg)
if len(result.Matched) != 1 {
t.Fatalf("expected 1 match, got %d", len(result.Matched))
}
if result.Matched[0].CrossRepo {
t.Error("expected same-repo match (not cross-repo)")
}
}
func TestMatch_MultipleProvidersSingleConsumer(t *testing.T) {
reg := NewRegistry()
// Two providers for the same route (e.g., two microservices behind
// a gateway). All three repos declare the same WorkspaceID="acme"
// so the boundary lets the matcher pair them.
reg.Add(Contract{
ID: "http::GET::/api/users",
Type: ContractHTTP,
Role: RoleProvider,
SymbolID: "svc-a::listUsers",
FilePath: "a.go",
RepoPrefix: "svc-a",
WorkspaceID: "acme",
ProjectID: "users",
})
reg.Add(Contract{
ID: "http::GET::/api/users",
Type: ContractHTTP,
Role: RoleProvider,
SymbolID: "svc-c::listUsers",
FilePath: "c.go",
RepoPrefix: "svc-c",
WorkspaceID: "acme",
ProjectID: "users",
})
reg.Add(Contract{
ID: "http::GET::/api/users",
Type: ContractHTTP,
Role: RoleConsumer,
SymbolID: "svc-b::fetchUsers",
FilePath: "b.go",
RepoPrefix: "svc-b",
WorkspaceID: "acme",
ProjectID: "users",
})
result := Match(reg)
// Consumer should be paired with each provider.
if len(result.Matched) != 2 {
t.Fatalf("expected 2 matches, got %d", len(result.Matched))
}
for _, m := range result.Matched {
if !m.CrossRepo {
t.Error("expected cross-repo match")
}
}
}
func TestMatch_EmptyRegistry(t *testing.T) {
reg := NewRegistry()
result := Match(reg)
if len(result.Matched) != 0 {
t.Errorf("expected 0 matches, got %d", len(result.Matched))
}
if len(result.OrphanProviders) != 0 {
t.Errorf("expected 0 orphan providers, got %d", len(result.OrphanProviders))
}
if len(result.OrphanConsumers) != 0 {
t.Errorf("expected 0 orphan consumers, got %d", len(result.OrphanConsumers))
}
}
// trpcContract builds a tRPC Contract whose Meta mirrors what
// TRPCExtractor emits, so the canonical-join tests exercise the same
// router/procedure shape the real extractor produces.
func trpcContract(role Role, router, procedure, repo string) Contract {
id := "trpc::" + router + "." + procedure
meta := map[string]any{"framework": "trpc", "router": router, "procedure": procedure}
if role == RoleConsumer {
meta["method"] = "useQuery"
}
return Contract{
ID: id,
Type: ContractTRPC,
Role: role,
SymbolID: repo + "::" + procedure,
FilePath: repo + "/trpc.ts",
RepoPrefix: repo,
WorkspaceID: "acme",
ProjectID: "trpc",
Meta: meta,
Confidence: 0.9,
}
}
// TestMatch_TRPCCanonicalJoin_CrossRepoRouterSpelling: the server names
// the router `userRouter`; the consumer in another repo reaches the same
// `getUser` procedure through a differently-spelled namespace
// (`trpc.api.getUser`). Exact-ID bucketing misses (the IDs differ in the
// router segment); the tRPC canonical join must pair them as a cross-
// repo link grouped under the provider's ID.
func TestMatch_TRPCCanonicalJoin_CrossRepoRouterSpelling(t *testing.T) {
reg := NewRegistry()
reg.Add(trpcContract(RoleProvider, "userRouter", "getUser", "svc-api"))
reg.Add(trpcContract(RoleConsumer, "api", "getUser", "webapp"))
result := Match(reg)
if len(result.Matched) != 1 {
t.Fatalf("expected 1 canonical-joined match, got %d (orphanP=%d orphanC=%d)",
len(result.Matched), len(result.OrphanProviders), len(result.OrphanConsumers))
}
m := result.Matched[0]
if m.ContractID != "trpc::userRouter.getUser" {
t.Errorf("group ID should be the provider's ID, got %s", m.ContractID)
}
if !m.CrossRepo {
t.Error("expected cross-repo join (different RepoPrefix)")
}
if m.Provider.RepoPrefix != "svc-api" || m.Consumer.RepoPrefix != "webapp" {
t.Errorf("wrong sides: provider=%s consumer=%s", m.Provider.RepoPrefix, m.Consumer.RepoPrefix)
}
if len(result.OrphanProviders) != 0 || len(result.OrphanConsumers) != 0 {
t.Errorf("joined contracts must leave the orphan lists: providers=%d consumers=%d",
len(result.OrphanProviders), len(result.OrphanConsumers))
}
}
// TestMatch_TRPCCanonicalJoin_RouterCasing: the router differs only by
// casing (`UserRouter` vs `userRouter`) for the same procedure. The
// lowercased-procedure key tolerates it and the pair joins.
func TestMatch_TRPCCanonicalJoin_RouterCasing(t *testing.T) {
reg := NewRegistry()
reg.Add(trpcContract(RoleProvider, "userRouter", "getUser", "svc-api"))
reg.Add(trpcContract(RoleConsumer, "UserRouter", "getUser", "webapp"))
result := Match(reg)
if len(result.Matched) != 1 {
t.Fatalf("router-casing difference should still join, got %d matches", len(result.Matched))
}
if !result.Matched[0].CrossRepo {
t.Error("expected cross-repo join")
}
}
// TestMatch_TRPCCohortIsolation: a tRPC consumer and a gRPC provider
// whose canonical keys look coincidentally similar (both mention
// `getUser`) must NOT pair — tRPC is its own cohort and never crosses
// into the RPC IDL family. Both stay orphaned.
func TestMatch_TRPCCohortIsolation(t *testing.T) {
reg := NewRegistry()
// gRPC service-level provider named "getUser" (would, within its own
// family, grab any consumer of service getUser).
reg.Add(Contract{
ID: "grpc::getUser",
Type: ContractGRPC,
Role: RoleProvider,
FilePath: "svc.proto",
RepoPrefix: "svc-grpc",
WorkspaceID: "acme",
ProjectID: "trpc",
Meta: map[string]any{"service": "getUser"},
})
reg.Add(trpcContract(RoleConsumer, "api", "getUser", "webapp"))
result := Match(reg)
if len(result.Matched) != 0 {
t.Fatalf("tRPC consumer must not pair a gRPC provider: %+v", result.Matched)
}
if len(result.OrphanProviders) != 1 || len(result.OrphanConsumers) != 1 {
t.Errorf("both sides stay orphaned across cohorts: providers=%d consumers=%d",
len(result.OrphanProviders), len(result.OrphanConsumers))
}
}
// TestMatch_TRPCCanonicalJoin_AmbiguousProcedureSkips documents the
// precision-over-recall tradeoff of keying on the procedure name alone:
// when the SAME procedure name is offered under two distinct provider
// routers, a plain consumer call can't say which it meant, so the join
// refuses to guess and leaves everything orphaned. (A procedure offered
// by exactly one router still joins — see the cross-repo test above.)
func TestMatch_TRPCCanonicalJoin_AmbiguousProcedureSkips(t *testing.T) {
reg := NewRegistry()
reg.Add(trpcContract(RoleProvider, "userRouter", "getUser", "svc-api"))
reg.Add(trpcContract(RoleProvider, "adminRouter", "getUser", "svc-api"))
reg.Add(trpcContract(RoleConsumer, "api", "getUser", "webapp"))
result := Match(reg)
if len(result.Matched) != 0 {
t.Fatalf("ambiguous procedure (two routers) must not join: %+v", result.Matched)
}
if len(result.OrphanProviders) != 2 || len(result.OrphanConsumers) != 1 {
t.Errorf("ambiguous procedure leaves all orphaned: providers=%d consumers=%d",
len(result.OrphanProviders), len(result.OrphanConsumers))
}
}
// TestMatch_TRPCCanonicalJoin_RespectsBoundary: the tRPC canonical join
// honours the same (workspace, project) boundary as exact matching —
// across-workspace contracts never join even when the procedure matches.
func TestMatch_TRPCCanonicalJoin_RespectsBoundary(t *testing.T) {
reg := NewRegistry()
prov := trpcContract(RoleProvider, "userRouter", "getUser", "svc-api")
cons := trpcContract(RoleConsumer, "api", "getUser", "webapp")
cons.WorkspaceID = "globex" // different workspace — must NOT join
reg.Add(prov)
reg.Add(cons)
result := Match(reg)
if len(result.Matched) != 0 {
t.Fatalf("across-workspace tRPC contracts must not join: %+v", result.Matched)
}
if len(result.OrphanProviders) != 1 || len(result.OrphanConsumers) != 1 {
t.Errorf("both sides stay orphaned: providers=%d consumers=%d",
len(result.OrphanProviders), len(result.OrphanConsumers))
}
}
func TestRegistry_AddAll(t *testing.T) {
reg := NewRegistry()
contracts := []Contract{
{ID: "http::GET::/a", Role: RoleProvider, FilePath: "a.go"},
{ID: "http::GET::/b", Role: RoleProvider, FilePath: "b.go"},
}
reg.AddAll(contracts, "myrepo")
byRepo := reg.ByRepo("myrepo")
if len(byRepo) != 2 {
t.Fatalf("expected 2 contracts in repo, got %d", len(byRepo))
}
for _, c := range byRepo {
if c.RepoPrefix != "myrepo" {
t.Errorf("expected repo prefix myrepo, got %s", c.RepoPrefix)
}
}
}
func TestRegistry_EvictRepo(t *testing.T) {
reg := NewRegistry()
reg.Add(Contract{ID: "http::GET::/a", Role: RoleProvider, FilePath: "a.go", RepoPrefix: "svc-a", SymbolID: "fn1"})
reg.Add(Contract{ID: "http::GET::/a", Role: RoleConsumer, FilePath: "b.go", RepoPrefix: "svc-b", SymbolID: "fn2"})
removed := reg.EvictRepo("svc-a")
if removed != 1 {
t.Fatalf("expected 1 removed, got %d", removed)
}
byID := reg.ByID("http::GET::/a")
if len(byID) != 1 {
t.Fatalf("expected 1 remaining, got %d", len(byID))
}
if byID[0].RepoPrefix != "svc-b" {
t.Errorf("wrong remaining contract: %s", byID[0].RepoPrefix)
}
}