From fcb4efab13739bfd7799b02f69c4261381433f79 Mon Sep 17 00:00:00 2001 From: OpsLevel Coding Agent Date: Fri, 16 Jan 2026 16:09:59 +0000 Subject: [PATCH] Changes made by OpsLevel coding agent. --- .github/workflows/mcp-eval-tests.yml | 35 +++++ README.md | 64 ++++++++ src/cmd/root_test.go | 218 +++++++++++++++++++++++++++ src/cmd/version_test.go | 116 ++++++++++++++ src/main_test.go | 27 ++++ 5 files changed, 460 insertions(+) create mode 100644 .github/workflows/mcp-eval-tests.yml create mode 100644 src/cmd/root_test.go create mode 100644 src/cmd/version_test.go create mode 100644 src/main_test.go diff --git a/.github/workflows/mcp-eval-tests.yml b/.github/workflows/mcp-eval-tests.yml new file mode 100644 index 0000000..34035bb --- /dev/null +++ b/.github/workflows/mcp-eval-tests.yml @@ -0,0 +1,35 @@ +name: MCP Eval Tests + +on: + push: + branches: [ main ] + pull_request: + paths: + - 'mcp-eval/**' + - .github/workflows/mcp-eval-tests.yml + +jobs: + test: + runs-on: ubuntu-latest + defaults: + run: + working-directory: mcp-eval + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: 'mcp-eval/.node-version' + cache: 'yarn' + cache-dependency-path: 'mcp-eval/yarn.lock' + + - name: Install dependencies + run: yarn install --frozen-lockfile + + - name: Run linting (prettier) + run: yarn lint + + - name: Run tests + run: yarn test diff --git a/README.md b/README.md index 4eda29a..abc1f21 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,10 @@ License Release + + Tests + + MCP Eval Tests Stability: Active @@ -201,3 +205,63 @@ If you didn't install the binary directly and instead pulled the docker image yo "public.ecr.aws/opslevel/mcp:latest" ], ``` + +# Development + +## Testing + +This repository uses a comprehensive test suite to ensure code quality and functionality. + +### Running Tests + +#### Go Tests + +The Go codebase (main MCP server) includes unit tests for core functionality: + +```bash +# Run all Go tests +cd src +go test ./... + +# Run tests with coverage +go test -race -coverprofile=coverage.txt -covermode=atomic ./... + +# Run tests with verbose output +go test -v ./... +``` + +#### JavaScript Tests + +The `mcp-eval` evaluation framework includes Jest-based tests: + +```bash +# Run all JavaScript tests +cd mcp-eval +npm test + +# Run tests in watch mode +npm test -- --watch +``` + +#### Using Task + +The repository uses [Task](https://taskfile.dev/) as a task runner. To run the full CI pipeline locally: + +```bash +# Run linting and tests +task ci + +# Run only tests +task test + +# Run only linting +task lint +``` + +### Continuous Integration + +All tests are automatically run on: +- Pull requests to the main branch +- Pushes to the main branch + +The CI pipeline runs linting, formatting checks, and the complete test suite to ensure code quality. diff --git a/src/cmd/root_test.go b/src/cmd/root_test.go new file mode 100644 index 0000000..aa83e2f --- /dev/null +++ b/src/cmd/root_test.go @@ -0,0 +1,218 @@ +package cmd + +import ( +"encoding/json" +"testing" + +"github.com/opslevel/opslevel-go/v2025" +) + +func TestNewToolResult(t *testing.T) { +// Test successful result with simple data +data := map[string]string{"key": "value"} +result, err := newToolResult(data, nil) + +if err != nil { +t.Errorf("Expected no error, got %v", err) +} + +if result == nil { +t.Fatal("Expected result to not be nil") +} + +// Test with error +testErr := opslevel.NewGQLError(nil, "test error") +result, err = newToolResult(nil, testErr) + +if err != nil { +t.Errorf("Expected no error from newToolResult, got %v", err) +} + +if result == nil { +t.Fatal("Expected result to not be nil") +} +} + +func TestSerializedComponentStructure(t *testing.T) { +// Test that serializedComponent can be instantiated and marshaled +component := serializedComponent{ +Id: "test-id", +Framework: "Django", +Language: "Python", +Name: "test-service", +Owner: "test-team", +Url: "https://example.com", +Level: serializedLevel{ +Alias: "Gold", +Index: 2, +}, +Lifecycle: serializedLifecycle{ +Alias: "Production", +Index: 3, +}, +Tier: serializedTier{ +Alias: "Tier 1", +Index: 0, +}, +} + +// Marshal to JSON to verify structure +data, err := json.Marshal(component) +if err != nil { +t.Errorf("Failed to marshal component: %v", err) +} + +// Unmarshal to verify round-trip +var decoded serializedComponent +err = json.Unmarshal(data, &decoded) +if err != nil { +t.Errorf("Failed to unmarshal component: %v", err) +} + +if decoded.Name != "test-service" { +t.Errorf("Expected Name 'test-service', got '%s'", decoded.Name) +} + +if decoded.Language != "Python" { +t.Errorf("Expected Language 'Python', got '%s'", decoded.Language) +} +} + +func TestSerializedCheckStructure(t *testing.T) { +// Test that serializedCheck can be instantiated and marshaled +check := serializedCheck{ +Id: "check-id", +Name: "test-check", +Owner: "test-team", +Description: "Test description", +Notes: "Test notes", +Enabled: true, +Type: "manual", +Level: serializedLevel{ +Alias: "Silver", +Index: 1, +}, +Category: "Security", +} + +data, err := json.Marshal(check) +if err != nil { +t.Errorf("Failed to marshal check: %v", err) +} + +var decoded serializedCheck +err = json.Unmarshal(data, &decoded) +if err != nil { +t.Errorf("Failed to unmarshal check: %v", err) +} + +if decoded.Name != "test-check" { +t.Errorf("Expected Name 'test-check', got '%s'", decoded.Name) +} + +if !decoded.Enabled { +t.Error("Expected Enabled to be true") +} +} + +func TestSerializedInfrastructureResourceStructure(t *testing.T) { +// Test that serializedInfrastructureResource can be instantiated +resource := serializedInfrastructureResource{ +Id: "infra-id", +Name: "test-db", +Owner: "test-team", +Aliases: []string{"alias1", "alias2"}, +Schema: "postgresql", +ProviderType: "aws", +} + +data, err := json.Marshal(resource) +if err != nil { +t.Errorf("Failed to marshal infrastructure resource: %v", err) +} + +var decoded serializedInfrastructureResource +err = json.Unmarshal(data, &decoded) +if err != nil { +t.Errorf("Failed to unmarshal infrastructure resource: %v", err) +} + +if decoded.Name != "test-db" { +t.Errorf("Expected Name 'test-db', got '%s'", decoded.Name) +} + +if len(decoded.Aliases) != 2 { +t.Errorf("Expected 2 aliases, got %d", len(decoded.Aliases)) +} +} + +func TestAllAccountMetadataStrings(t *testing.T) { +metadataTypes := AllAccountMetadataStrings() + +if len(metadataTypes) == 0 { +t.Error("Expected at least one metadata type, got none") +} + +// Verify expected types are present +expectedTypes := []string{"lifecycles", "levels", "tiers", "componentTypes"} +for _, expected := range expectedTypes { +found := false +for _, actual := range metadataTypes { +if actual == expected { +found = true +break +} +} +if !found { +t.Errorf("Expected metadata type '%s' not found", expected) +} +} +} + +func TestComponentFilterStructure(t *testing.T) { +// Test simple filter +filter := componentFilter{ +Key: "name", +Type: "equals", +Arg: "service-name", +} + +data, err := json.Marshal(filter) +if err != nil { +t.Errorf("Failed to marshal filter: %v", err) +} + +var decoded componentFilter +err = json.Unmarshal(data, &decoded) +if err != nil { +t.Errorf("Failed to unmarshal filter: %v", err) +} + +if decoded.Key != "name" { +t.Errorf("Expected Key 'name', got '%s'", decoded.Key) +} + +// Test composite filter +compositeFilter := componentFilter{ +Connective: "and", +Predicates: []componentFilter{ +{Key: "language", Type: "equals", Arg: "Python"}, +{Key: "owner_id", Type: "equals", Arg: "team-123"}, +}, +} + +data, err = json.Marshal(compositeFilter) +if err != nil { +t.Errorf("Failed to marshal composite filter: %v", err) +} + +var decodedComposite componentFilter +err = json.Unmarshal(data, &decodedComposite) +if err != nil { +t.Errorf("Failed to unmarshal composite filter: %v", err) +} + +if len(decodedComposite.Predicates) != 2 { +t.Errorf("Expected 2 predicates, got %d", len(decodedComposite.Predicates)) +} +} diff --git a/src/cmd/version_test.go b/src/cmd/version_test.go new file mode 100644 index 0000000..9c5cf08 --- /dev/null +++ b/src/cmd/version_test.go @@ -0,0 +1,116 @@ +package cmd + +import ( +"runtime" +"testing" +) + +func TestGetGoInfo(t *testing.T) { +goInfo := getGoInfo() + +if goInfo.Version == "" { +t.Error("Expected Go version to be set, got empty string") +} + +if goInfo.Compiler == "" { +t.Error("Expected Go compiler to be set, got empty string") +} + +if goInfo.OS == "" { +t.Error("Expected Go OS to be set, got empty string") +} + +if goInfo.Arch == "" { +t.Error("Expected Go Arch to be set, got empty string") +} + +// Verify values match runtime package +if goInfo.Version != runtime.Version() { +t.Errorf("Expected Version %s, got %s", runtime.Version(), goInfo.Version) +} + +if goInfo.Compiler != runtime.Compiler { +t.Errorf("Expected Compiler %s, got %s", runtime.Compiler, goInfo.Compiler) +} + +if goInfo.OS != runtime.GOOS { +t.Errorf("Expected OS %s, got %s", runtime.GOOS, goInfo.OS) +} + +if goInfo.Arch != runtime.GOARCH { +t.Errorf("Expected Arch %s, got %s", runtime.GOARCH, goInfo.Arch) +} +} + +func TestInitBuild(t *testing.T) { +// Save original values +originalVersion := version +originalCommit := commit + +// Test with normal commit +version = "1.0.0" +commit = "abc123def456789" +initBuild() + +if build.Version != "1.0.0" { +t.Errorf("Expected build.Version to be '1.0.0', got '%s'", build.Version) +} + +if build.Commit != "abc123def456" { +t.Errorf("Expected build.Commit to be truncated to 12 chars, got '%s'", build.Commit) +} + +// Test with short commit +commit = "short" +initBuild() + +if build.Commit != "short" { +t.Errorf("Expected build.Commit to be 'short', got '%s'", build.Commit) +} + +// Restore original values +version = originalVersion +commit = originalCommit +} + +func TestBuildStructure(t *testing.T) { +// Test that Build struct can be instantiated +b := Build{ +Version: "1.0.0", +Commit: "abc123", +GoInfo: GoInfo{ +Version: "go1.20", +Compiler: "gc", +OS: "linux", +Arch: "amd64", +}, +} + +if b.Version != "1.0.0" { +t.Errorf("Expected Version '1.0.0', got '%s'", b.Version) +} + +if b.Commit != "abc123" { +t.Errorf("Expected Commit 'abc123', got '%s'", b.Commit) +} + +if b.GoInfo.Version != "go1.20" { +t.Errorf("Expected GoInfo.Version 'go1.20', got '%s'", b.GoInfo.Version) +} +} + +func TestOpslevelVersionStructure(t *testing.T) { +// Test that OpslevelVersion struct can be instantiated +ov := OpslevelVersion{ +Version: "1.0.0", +Commit: "abc123", +} + +if ov.Version != "1.0.0" { +t.Errorf("Expected Version '1.0.0', got '%s'", ov.Version) +} + +if ov.Commit != "abc123" { +t.Errorf("Expected Commit 'abc123', got '%s'", ov.Commit) +} +} diff --git a/src/main_test.go b/src/main_test.go new file mode 100644 index 0000000..9388f87 --- /dev/null +++ b/src/main_test.go @@ -0,0 +1,27 @@ +package main + +import ( +"testing" +) + +func TestVersionVariables(t *testing.T) { +// Test that version variables are defined +if version == "" { +t.Log("version is empty (expected for dev builds)") +} + +if commit == "" { +t.Log("commit is empty (expected for dev builds)") +} + +// At minimum, these should be defined as strings +if len(version) >= 0 && len(commit) >= 0 { +t.Log("Version variables are properly defined") +} +} + +func TestMainPackageExists(t *testing.T) { +// This test simply verifies that the main package can be imported and tested +// This ensures the basic structure is correct +t.Log("Main package is accessible for testing") +}