Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .trunk/configs/.golangci.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,13 @@
"exclusions": {
"generated": "lax",
"paths": ["third_party$", "builtin$", "examples$"],
"presets": ["comments", "common-false-positives", "legacy", "std-error-handling"]
"presets": ["comments", "common-false-positives", "legacy", "std-error-handling"],
"rules": [
{
"linters": ["staticcheck"],
"text": "QF1001"
}
]
}
},
"run": {
Expand Down
35 changes: 17 additions & 18 deletions .trunk/trunk.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,13 @@ cli:
plugins:
sources:
- id: trunk
ref: v1.7.2
ref: v1.7.6
uri: https://github.com/trunk-io/plugins

# Many linters and tools depend on runtimes - configure them here. (https://docs.trunk.io/runtimes)
runtimes:
enabled:
- go@1.25.6
- node@22.16.0
- python@3.10.8
- go@1.25.7

# This is the section where you manage your linters. (https://docs.trunk.io/check/configuration)
lint:
Expand All @@ -26,25 +24,26 @@ lint:
- contrib/**
- protos/pb/pb.pb.go
enabled:
- svgo@4.0.0
- golangci-lint2@2.4.0
- taplo@0.10.0
- svgo@4.0.1
- golangci-lint2@2.11.3
- trivy@0.69.3
- actionlint@1.7.7
- checkov@3.2.467
- dotenv-linter@3.3.0
- actionlint@1.7.11
- checkov@3.2.510
- dotenv-linter@4.0.0
- git-diff-check
- gofmt@1.20.4
- hadolint@2.12.1-beta
- markdownlint@0.45.0
- osv-scanner@2.2.2
- oxipng@9.1.5
- prettier@3.6.2
- renovate@41.91.3
- hadolint@2.14.0
- markdownlint@0.48.0
- osv-scanner@2.3.3
- oxipng@10.1.0
- prettier@3.8.1
- renovate@43.84.0
- shellcheck@0.11.0
- shfmt@3.6.0
- tflint@0.59.1
- trufflehog@3.90.5
- yamllint@1.37.1
- tflint@0.61.0
- trufflehog@3.94.0
- yamllint@1.38.0
actions:
enabled:
- trunk-announce
Expand Down
57 changes: 57 additions & 0 deletions graphql/e2e/federation/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Federation test setup with 2 alpha instances
# alpha1: user-service subgraph
# alpha2: reviews-service subgraph
services:
alpha1:
image: dgraph/dgraph:local
working_dir: /data/alpha1
labels:
cluster: test
service: alpha1
ports:
- 8080
- 9080
volumes:
- type: bind
source: ${LINUX_GOBIN:-$GOPATH/bin}
target: /gobin
read_only: true
command:
/gobin/dgraph ${COVERAGE_OUTPUT} alpha --my=alpha1:7080 --zero=zero1:5080 --logtostderr -v=2
--raft="idx=1;" --security "whitelist=0.0.0.0/0;"
alpha2:
image: dgraph/dgraph:local
working_dir: /data/alpha2
depends_on:
- alpha1
labels:
cluster: test
service: alpha2
ports:
- 8080
- 9080
volumes:
- type: bind
source: ${LINUX_GOBIN:-$GOPATH/bin}
target: /gobin
read_only: true
command:
/gobin/dgraph ${COVERAGE_OUTPUT} alpha --my=alpha2:7080 --zero=zero1:5080 --logtostderr -v=2
--raft="idx=2;" --security "whitelist=0.0.0.0/0;"
zero1:
image: dgraph/dgraph:local
working_dir: /data/zero1
labels:
cluster: test
ports:
- 5080
- 6080
volumes:
- type: bind
source: ${LINUX_GOBIN:-$GOPATH/bin}
target: /gobin
read_only: true
command:
/gobin/dgraph ${COVERAGE_OUTPUT} zero --telemetry "reports=false;" --raft="idx=1;"
--my=zero1:5080 --replicas=1 --logtostderr -v=2 --bindall
volumes: {}
185 changes: 185 additions & 0 deletions graphql/e2e/federation/federation_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
//go:build integration

/*
* SPDX-FileCopyrightText: © 2017-2026 Istari Digital, Inc.
* SPDX-License-Identifier: Apache-2.0
*/

package federation

import (
"context"
"encoding/json"
"fmt"
"net/http"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"time"

"github.com/stretchr/testify/require"

"github.com/dgraph-io/dgraph/v25/graphql/e2e/common"
"github.com/dgraph-io/dgraph/v25/testutil"
"github.com/dgraph-io/dgraph/v25/x"
)

var (
// Alpha1 serves the user-service subgraph
Alpha1HTTP = testutil.ContainerAddr("alpha1", 8080)
Alpha1GraphqlURL = "http://" + Alpha1HTTP + "/graphql"
Alpha1GraphqlAdminURL = "http://" + Alpha1HTTP + "/admin"

// Alpha2 serves the reviews-service subgraph
Alpha2HTTP = testutil.ContainerAddr("alpha2", 8080)
Alpha2GraphqlURL = "http://" + Alpha2HTTP + "/graphql"
Alpha2GraphqlAdminURL = "http://" + Alpha2HTTP + "/admin"
)

// TestMain sets up the test environment with two Dgraph alpha instances
func TestMain(m *testing.M) {
userSchema, err := os.ReadFile("testdata/user-service.graphql")
x.Panic(err)

reviewsSchema, err := os.ReadFile("testdata/reviews-service.graphql")
x.Panic(err)

// Wait for both alphas to be ready
err = common.CheckGraphQLStarted(Alpha1GraphqlAdminURL)
x.Panic(err)
err = common.CheckGraphQLStarted(Alpha2GraphqlAdminURL)
x.Panic(err)

// Bootstrap alpha1 with user-service schema
common.BootstrapServer(userSchema, nil)

// Deploy reviews-service schema to alpha2
// Retry until schema is successfully deployed
maxRetries := 30
for i := 0; i < maxRetries; i++ {
resp, updateErr := http.Post("http://"+Alpha2HTTP+"/admin/schema",
"application/graphql", strings.NewReader(string(reviewsSchema)))
if updateErr == nil {
resp.Body.Close()
if resp.StatusCode == 200 {
// Wait a bit for schema to be loaded
time.Sleep(500 * time.Millisecond)
break
}
}
if resp != nil && resp.Body != nil {
resp.Body.Close()
}
if i == maxRetries-1 {
x.Panic(fmt.Errorf("failed to deploy schema to alpha2 after %d retries", maxRetries))
}
time.Sleep(200 * time.Millisecond)
}

os.Exit(m.Run())
}

// TestRoverFederationComposition tests Apollo Federation supergraph composition
// with two Dgraph instances serving different federated subgraphs.
//
// This test:
// 1. Fetches SDL via _service query from alpha1 (user-service) and alpha2 (reviews-service)
// 2. Runs Apollo Rover CLI to compose a supergraph
// 3. Verifies composition succeeds (auxiliary types should be excluded for @extends types)
func TestRoverFederationComposition(t *testing.T) {

// Test that we can run docker
testCmd := exec.Command("docker", "version")
if err := testCmd.Run(); err != nil {
t.Skip("Skipping test: docker is not running or accessible")
}

testdataDir := "./testdata"
absTestdataDir, err := filepath.Abs(testdataDir)
require.NoError(t, err)

// Fetch SDL from alpha1 (user-service)
serviceQuery := &common.GraphQLParams{
Query: `query { _service { sdl } }`,
}

userServiceResp := serviceQuery.ExecuteAsPost(t, Alpha1GraphqlURL)
common.RequireNoGQLErrors(t, userServiceResp)

var userServiceResult struct {
Service struct {
SDL string `json:"sdl"`
} `json:"_service"`
}
require.NoError(t, json.Unmarshal(userServiceResp.Data, &userServiceResult))

// Fetch SDL from alpha2 (reviews-service)
reviewsServiceResp := serviceQuery.ExecuteAsPost(t, Alpha2GraphqlURL)
common.RequireNoGQLErrors(t, reviewsServiceResp)

var reviewsServiceResult struct {
Service struct {
SDL string `json:"sdl"`
} `json:"_service"`
}
require.NoError(t, json.Unmarshal(reviewsServiceResp.Data, &reviewsServiceResult))

userSDL := userServiceResult.Service.SDL
reviewsSDL := reviewsServiceResult.Service.SDL

// Write the SDL files that Rover will use
userSDLFile := filepath.Join(testdataDir, "user-service-generated.graphql")
require.NoError(t, os.WriteFile(userSDLFile, []byte(userSDL), 0644))
defer os.Remove(userSDLFile)

reviewsSDLFile := filepath.Join(testdataDir, "reviews-service-generated.graphql")
require.NoError(t, os.WriteFile(reviewsSDLFile, []byte(reviewsSDL), 0644))
defer os.Remove(reviewsSDLFile)

// Create supergraph config that points to the generated SDL files
supergraphConfig := fmt.Sprintf(`federation_version: =2.7.1
subgraphs:
users:
routing_url: %s
schema:
file: ./user-service-generated.graphql
reviews:
routing_url: %s
schema:
file: ./reviews-service-generated.graphql
`, Alpha1GraphqlURL, Alpha2GraphqlURL)
supergraphFile := filepath.Join(testdataDir, "supergraph-generated.yml")
require.NoError(t, os.WriteFile(supergraphFile, []byte(supergraphConfig), 0644))
defer os.Remove(supergraphFile)

// Run rover supergraph compose using Docker
ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
defer cancel()

cmd := exec.CommandContext(ctx, "docker", "run", "--rm",
"-e", "APOLLO_ELV2_LICENSE=accept",
"-v", absTestdataDir+":/workspace",
"-w", "/workspace",
"node:24-slim",
"sh", "-c",
`apt-get update -qq && apt-get install -y -qq curl > /dev/null 2>&1 && \
curl -sSL https://rover.apollo.dev/nix/latest | sh > /dev/null 2>&1 && \
$HOME/.rover/bin/rover supergraph compose --config supergraph-generated.yml`)

output, err := cmd.CombinedOutput()
outputStr := string(output)

// The auxiliary types for extended User should NOT be generated from alpha2 (reviews-service)
if err != nil {
if strings.Contains(outputStr, "UserPatch") ||
strings.Contains(outputStr, "UserOrderable") ||
strings.Contains(outputStr, "UserHasFilter") {
t.Errorf("Rover composition failed with auxiliary type conflicts. "+
"The fix should exclude these types for @extends. Error: %v", err)
} else {
t.Errorf("Rover composition failed with unexpected error: %v", err)
}
}
}
10 changes: 10 additions & 0 deletions graphql/e2e/federation/testdata/reviews-service.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
type Review {
id: ID!
rating: Int! @search
comment: String @search(by: [fulltext])
}

extend type User @key(fields: "userId") {
userId: ID! @external
reviews: [Review]
}
5 changes: 5 additions & 0 deletions graphql/e2e/federation/testdata/user-service.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
type User @key(fields: "userId") {
userId: ID!
username: String! @search(by: [hash])
email: String
}
Loading
Loading