From 72d4fe8e5f4d46fef48202ac0c5ecee79bc5bbd1 Mon Sep 17 00:00:00 2001 From: Yann Brillouet Date: Fri, 3 Jul 2026 10:20:27 +0200 Subject: [PATCH 1/3] tests(graphql): Add interface-implementation method alignment tests --- .../resolvers/cluster_vulnerabilities_test.go | 23 +++++ .../resolvers/image_components_test.go | 21 ++++ .../graphql/resolvers/image_resolver_test.go | 20 ++++ .../resolvers/image_vulnerabilities_test.go | 24 +++++ .../graphql/resolvers/node_components_test.go | 13 +++ .../resolvers/node_vulnerabilities_test.go | 25 +++++ central/graphql/resolvers/test_utils_test.go | 95 +++++++++++++++++++ 7 files changed, 221 insertions(+) create mode 100644 central/graphql/resolvers/cluster_vulnerabilities_test.go create mode 100644 central/graphql/resolvers/image_components_test.go create mode 100644 central/graphql/resolvers/image_resolver_test.go create mode 100644 central/graphql/resolvers/image_vulnerabilities_test.go create mode 100644 central/graphql/resolvers/node_components_test.go create mode 100644 central/graphql/resolvers/node_vulnerabilities_test.go create mode 100644 central/graphql/resolvers/test_utils_test.go diff --git a/central/graphql/resolvers/cluster_vulnerabilities_test.go b/central/graphql/resolvers/cluster_vulnerabilities_test.go new file mode 100644 index 0000000000000..3900e361009a8 --- /dev/null +++ b/central/graphql/resolvers/cluster_vulnerabilities_test.go @@ -0,0 +1,23 @@ +package resolvers + +import ( + "reflect" + "testing" +) + +func TestClusterVulnerabilityResolverType(t *testing.T) { + // The implementation type has four more methods than the interface it implements. + // + // 17 - SnoozeExpiry + // 18 - SnoozeStart + // 19 - Snoozed + // 24 - Type + // + // This can break graphQL queries to the system until + // https://github.com/graph-gophers/graphql-go/issues/763 is fixed. + t.Skip("Interface and implementation types do not have aligned method indices.") + resolverInterface := reflect.TypeOf((*ClusterVulnerabilityResolver)(nil)).Elem() + resolverImplType := reflect.TypeOf((*clusterCVEResolver)(nil)) + + validateAlignedMethodIndex(t, resolverInterface, resolverImplType) +} diff --git a/central/graphql/resolvers/image_components_test.go b/central/graphql/resolvers/image_components_test.go new file mode 100644 index 0000000000000..ae22d2b686868 --- /dev/null +++ b/central/graphql/resolvers/image_components_test.go @@ -0,0 +1,21 @@ +package resolvers + +import ( + "reflect" + "testing" +) + +func TestImageComponentResolverType(t *testing.T) { + // The implementation type has two more methods than the interface it implements. + // + // 0 - Architecture + // 7 - ImageId + // + // This can break graphQL queries to the system until + // https://github.com/graph-gophers/graphql-go/issues/763 is fixed. + t.Skip("Interface and implementation types do not have aligned method indices.") + resolverInterface := reflect.TypeOf((*ImageComponentResolver)(nil)).Elem() + resolverImplType := reflect.TypeOf((*imageComponentV2Resolver)(nil)) + + validateAlignedMethodIndex(t, resolverInterface, resolverImplType) +} diff --git a/central/graphql/resolvers/image_resolver_test.go b/central/graphql/resolvers/image_resolver_test.go new file mode 100644 index 0000000000000..6d08c5d99b923 --- /dev/null +++ b/central/graphql/resolvers/image_resolver_test.go @@ -0,0 +1,20 @@ +package resolvers + +import ( + "reflect" + "testing" +) + +func TestImageResolverType(t *testing.T) { + resolverInterface := reflect.TypeOf((*ImageResolver)(nil)).Elem() + resolverImplType := reflect.TypeOf((*imageResolver)(nil)) + + validateAlignedMethodIndex(t, resolverInterface, resolverImplType) +} + +func TestImageV2ResolverType(t *testing.T) { + resolverInterface := reflect.TypeOf((*ImageResolver)(nil)).Elem() + resolverImplType := reflect.TypeOf((*imageV2Resolver)(nil)) + + validateAlignedMethodIndex(t, resolverInterface, resolverImplType) +} diff --git a/central/graphql/resolvers/image_vulnerabilities_test.go b/central/graphql/resolvers/image_vulnerabilities_test.go new file mode 100644 index 0000000000000..b9cddcf6a9bec --- /dev/null +++ b/central/graphql/resolvers/image_vulnerabilities_test.go @@ -0,0 +1,24 @@ +package resolvers + +import ( + "reflect" + "testing" +) + +func TestImageVulnerabilityResolverType(t *testing.T) { + // The implementation type has five more methods than the interface it implements. + // + // 2 - ComponentId + // 12 - FirstImageOccurrence + // 14 - ID + // 19 - ImageId + // 32 - State + // + // This can break graphQL queries to the system until + // https://github.com/graph-gophers/graphql-go/issues/763 is fixed. + t.Skip("Interface and implementation types do not have aligned method indices.") + resolverInterface := reflect.TypeOf((*ImageVulnerabilityResolver)(nil)).Elem() + resolverImplType := reflect.TypeOf((*imageCVEV2Resolver)(nil)) + + validateAlignedMethodIndex(t, resolverInterface, resolverImplType) +} diff --git a/central/graphql/resolvers/node_components_test.go b/central/graphql/resolvers/node_components_test.go new file mode 100644 index 0000000000000..bf86d8adbbbb2 --- /dev/null +++ b/central/graphql/resolvers/node_components_test.go @@ -0,0 +1,13 @@ +package resolvers + +import ( + "reflect" + "testing" +) + +func TestNodeComponentResolverType(t *testing.T) { + resolverInterface := reflect.TypeOf((*NodeComponentResolver)(nil)).Elem() + resolverImplType := reflect.TypeOf((*nodeComponentResolver)(nil)) + + validateAlignedMethodIndex(t, resolverInterface, resolverImplType) +} diff --git a/central/graphql/resolvers/node_vulnerabilities_test.go b/central/graphql/resolvers/node_vulnerabilities_test.go new file mode 100644 index 0000000000000..e94ba19d88135 --- /dev/null +++ b/central/graphql/resolvers/node_vulnerabilities_test.go @@ -0,0 +1,25 @@ +package resolvers + +import ( + "reflect" + "testing" +) + +func TestNodeVulnerabilityResolverType(t *testing.T) { + // The implementation type has six more methods than the interface it implements. + // + // 6 - ID + // 18 - Orphaned + // 19 - OrphanedTime + // 23 - SnoozeExpiry + // 24 - SnoozeStart + // 25 - Snoozed + // + // This can break graphQL queries to the system until + // https://github.com/graph-gophers/graphql-go/issues/763 is fixed. + t.Skip("Interface and implementation types do not have aligned method indices.") + resolverInterface := reflect.TypeOf((*NodeVulnerabilityResolver)(nil)).Elem() + resolverImplType := reflect.TypeOf((*nodeCVEResolver)(nil)) + + validateAlignedMethodIndex(t, resolverInterface, resolverImplType) +} diff --git a/central/graphql/resolvers/test_utils_test.go b/central/graphql/resolvers/test_utils_test.go new file mode 100644 index 0000000000000..42be901304bd4 --- /dev/null +++ b/central/graphql/resolvers/test_utils_test.go @@ -0,0 +1,95 @@ +package resolvers + +import ( + "reflect" + "testing" + + "github.com/stretchr/testify/assert" +) + +func validateAlignedMethodIndex(t testing.TB, type1 reflect.Type, type2 reflect.Type) { + t.Helper() + + if !assert.Equalf(t, type1.NumMethod(), type2.NumMethod(), + "Types %s and %s have should have the same number of methods", + type1.String(), type2.String(), + ) { + dumpTypeMethods(t, type1, type1.String(), false) + dumpTypeMethods(t, type2, type2.String(), false) + return + } + for i := range type1.NumMethod() { + m1 := type1.Method(i) + m2 := type2.Method(i) + assert.Equal(t, m1.Index, m2.Index) + assert.Equal(t, m1.Name, m2.Name) + } +} + +// dumpResolverMethods uses reflection to dump the index and names of methods for a given resolver type. +// It accepts any resolver value and outputs method information to the test logger. +// +// Example usage: +// +// // After creating a resolver instance in a test: +// componentResolver, err := resolver.ImageComponent(ctx, struct{ ID *graphql.ID }{ID: &componentID}) +// require.NoError(t, err) +// dumpResolverMethods(t, componentResolver, "imageComponentV2Resolver") +// +// Output includes: +// - Index of each method (0-based) +// - Method name +// - Full method signature +// - Input parameter types +// - Output parameter types +func dumpResolverMethods(t *testing.T, resolver interface{}, resolverName string) { + t.Helper() + + if resolver == nil { + t.Logf("Resolver %s is nil", resolverName) + return + } + + val := reflect.ValueOf(resolver) + typ := val.Type() + + dumpTypeMethods(t, typ, resolverName, true) +} + +func dumpTypeMethods(t testing.TB, typ reflect.Type, typeName string, verbose bool) { + t.Logf("\n=== Methods of %s ===", typeName) + t.Logf("Type: %s", typ.String()) + t.Logf("Kind: %s", typ.Kind()) + t.Logf("Number of methods: %d\n", typ.NumMethod()) + + for i := 0; i < typ.NumMethod(); i++ { + method := typ.Method(i) + t.Logf("[%d] %s", i, method.Name) + + if verbose { + // Also log the method signature + methodType := method.Type + t.Logf(" Signature: %s", methodType.String()) + + // Log input parameters + numIn := methodType.NumIn() + if numIn > 0 { + t.Logf(" Inputs (%d):", numIn) + for j := 0; j < numIn; j++ { + t.Logf(" [%d] %s", j, methodType.In(j).String()) + } + } + + // Log return parameters + numOut := methodType.NumOut() + if numOut > 0 { + t.Logf(" Outputs (%d):", numOut) + for j := 0; j < numOut; j++ { + t.Logf(" [%d] %s", j, methodType.Out(j).String()) + } + } + t.Logf("") + } + } + t.Logf("=== End of methods for %s ===\n", typeName) +} From 1b8003c953bb9ff78047a5d987863e2ea759d6b4 Mon Sep 17 00:00:00 2001 From: Yann Brillouet Date: Fri, 3 Jul 2026 15:55:59 +0200 Subject: [PATCH 2/3] Remove now unused method --- central/graphql/resolvers/test_utils_test.go | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/central/graphql/resolvers/test_utils_test.go b/central/graphql/resolvers/test_utils_test.go index 42be901304bd4..fd72cd81b7038 100644 --- a/central/graphql/resolvers/test_utils_test.go +++ b/central/graphql/resolvers/test_utils_test.go @@ -26,7 +26,7 @@ func validateAlignedMethodIndex(t testing.TB, type1 reflect.Type, type2 reflect. } } -// dumpResolverMethods uses reflection to dump the index and names of methods for a given resolver type. +// dumpTypeMethods uses reflection to dump the index and names of methods for a given type. // It accepts any resolver value and outputs method information to the test logger. // // Example usage: @@ -34,7 +34,7 @@ func validateAlignedMethodIndex(t testing.TB, type1 reflect.Type, type2 reflect. // // After creating a resolver instance in a test: // componentResolver, err := resolver.ImageComponent(ctx, struct{ ID *graphql.ID }{ID: &componentID}) // require.NoError(t, err) -// dumpResolverMethods(t, componentResolver, "imageComponentV2Resolver") +// dumpTypeMethods(t, reflect.TypeOf(componentResolver), "imageComponentV2Resolver") // // Output includes: // - Index of each method (0-based) @@ -42,20 +42,6 @@ func validateAlignedMethodIndex(t testing.TB, type1 reflect.Type, type2 reflect. // - Full method signature // - Input parameter types // - Output parameter types -func dumpResolverMethods(t *testing.T, resolver interface{}, resolverName string) { - t.Helper() - - if resolver == nil { - t.Logf("Resolver %s is nil", resolverName) - return - } - - val := reflect.ValueOf(resolver) - typ := val.Type() - - dumpTypeMethods(t, typ, resolverName, true) -} - func dumpTypeMethods(t testing.TB, typ reflect.Type, typeName string, verbose bool) { t.Logf("\n=== Methods of %s ===", typeName) t.Logf("Type: %s", typ.String()) From 65156ce9f067b40c5052106adfa59c2bf77234bf Mon Sep 17 00:00:00 2001 From: Yann Brillouet Date: Wed, 8 Jul 2026 11:49:34 +0200 Subject: [PATCH 3/3] Address review comments --- central/graphql/resolvers/cluster_vulnerabilities_test.go | 2 ++ central/graphql/resolvers/image_components_test.go | 2 ++ central/graphql/resolvers/image_vulnerabilities_test.go | 2 ++ central/graphql/resolvers/node_vulnerabilities_test.go | 2 ++ central/graphql/resolvers/test_utils_test.go | 1 - 5 files changed, 8 insertions(+), 1 deletion(-) diff --git a/central/graphql/resolvers/cluster_vulnerabilities_test.go b/central/graphql/resolvers/cluster_vulnerabilities_test.go index 3900e361009a8..62cf4adc9588f 100644 --- a/central/graphql/resolvers/cluster_vulnerabilities_test.go +++ b/central/graphql/resolvers/cluster_vulnerabilities_test.go @@ -15,6 +15,8 @@ func TestClusterVulnerabilityResolverType(t *testing.T) { // // This can break graphQL queries to the system until // https://github.com/graph-gophers/graphql-go/issues/763 is fixed. + // + // TODO(ROX-35654): Unskip this tests. t.Skip("Interface and implementation types do not have aligned method indices.") resolverInterface := reflect.TypeOf((*ClusterVulnerabilityResolver)(nil)).Elem() resolverImplType := reflect.TypeOf((*clusterCVEResolver)(nil)) diff --git a/central/graphql/resolvers/image_components_test.go b/central/graphql/resolvers/image_components_test.go index ae22d2b686868..8f93bfd5b5039 100644 --- a/central/graphql/resolvers/image_components_test.go +++ b/central/graphql/resolvers/image_components_test.go @@ -13,6 +13,8 @@ func TestImageComponentResolverType(t *testing.T) { // // This can break graphQL queries to the system until // https://github.com/graph-gophers/graphql-go/issues/763 is fixed. + // + // TODO(ROX-35654): Unskip this tests. t.Skip("Interface and implementation types do not have aligned method indices.") resolverInterface := reflect.TypeOf((*ImageComponentResolver)(nil)).Elem() resolverImplType := reflect.TypeOf((*imageComponentV2Resolver)(nil)) diff --git a/central/graphql/resolvers/image_vulnerabilities_test.go b/central/graphql/resolvers/image_vulnerabilities_test.go index b9cddcf6a9bec..e76ce4fbd721a 100644 --- a/central/graphql/resolvers/image_vulnerabilities_test.go +++ b/central/graphql/resolvers/image_vulnerabilities_test.go @@ -16,6 +16,8 @@ func TestImageVulnerabilityResolverType(t *testing.T) { // // This can break graphQL queries to the system until // https://github.com/graph-gophers/graphql-go/issues/763 is fixed. + // + // TODO(ROX-35654): Unskip this tests. t.Skip("Interface and implementation types do not have aligned method indices.") resolverInterface := reflect.TypeOf((*ImageVulnerabilityResolver)(nil)).Elem() resolverImplType := reflect.TypeOf((*imageCVEV2Resolver)(nil)) diff --git a/central/graphql/resolvers/node_vulnerabilities_test.go b/central/graphql/resolvers/node_vulnerabilities_test.go index e94ba19d88135..2dc1340d56742 100644 --- a/central/graphql/resolvers/node_vulnerabilities_test.go +++ b/central/graphql/resolvers/node_vulnerabilities_test.go @@ -17,6 +17,8 @@ func TestNodeVulnerabilityResolverType(t *testing.T) { // // This can break graphQL queries to the system until // https://github.com/graph-gophers/graphql-go/issues/763 is fixed. + // + // TODO(ROX-35654): Unskip this tests. t.Skip("Interface and implementation types do not have aligned method indices.") resolverInterface := reflect.TypeOf((*NodeVulnerabilityResolver)(nil)).Elem() resolverImplType := reflect.TypeOf((*nodeCVEResolver)(nil)) diff --git a/central/graphql/resolvers/test_utils_test.go b/central/graphql/resolvers/test_utils_test.go index fd72cd81b7038..83451dd54dd78 100644 --- a/central/graphql/resolvers/test_utils_test.go +++ b/central/graphql/resolvers/test_utils_test.go @@ -21,7 +21,6 @@ func validateAlignedMethodIndex(t testing.TB, type1 reflect.Type, type2 reflect. for i := range type1.NumMethod() { m1 := type1.Method(i) m2 := type2.Method(i) - assert.Equal(t, m1.Index, m2.Index) assert.Equal(t, m1.Name, m2.Name) } }