Publisher integration tests (48 tests): dry-run verification for all 8 publishers (GitHub, Docker, Homebrew, Scoop, AUR, Chocolatey, npm, LinuxKit), command building, config parsing, repository detection, artifact handling, cross-publisher name uniqueness, nil relCfg handling, checksum mapping, interface compliance. SDK generation tests (38 tests): orchestration, generator registry, interface compliance for all 4 languages (TypeScript, Python, Go, PHP), config defaults, SetVersion, spec detection priority across all 8 common paths. Breaking change detection tests (30 tests): oasdiff integration covering add/remove endpoints, required/optional params, response type changes, HTTP method removal, identical specs, multiple breaking changes, JSON format support, error handling, DiffExitCode, DiffResult structure. Co-Authored-By: Virgil <virgil@lethean.io>
337 lines
9.3 KiB
Go
337 lines
9.3 KiB
Go
package sdk
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"forge.lthn.ai/core/go-devops/sdk/generators"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// --- SDK Generation Orchestration Tests ---
|
|
|
|
func TestSDK_Generate_Good_AllLanguages(t *testing.T) {
|
|
t.Run("Generate iterates all configured languages", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
|
|
// Create a minimal OpenAPI spec
|
|
specPath := filepath.Join(tmpDir, "openapi.yaml")
|
|
err := os.WriteFile(specPath, []byte(minimalSpec), 0644)
|
|
require.NoError(t, err)
|
|
|
|
cfg := &Config{
|
|
Spec: "openapi.yaml",
|
|
Languages: []string{"nonexistent-lang"},
|
|
Output: "sdk",
|
|
Package: PackageConfig{
|
|
Name: "testclient",
|
|
Version: "1.0.0",
|
|
},
|
|
}
|
|
s := New(tmpDir, cfg)
|
|
s.SetVersion("v1.0.0")
|
|
|
|
// Generate should fail on unknown language
|
|
err = s.Generate(context.Background())
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "unknown language")
|
|
})
|
|
}
|
|
|
|
func TestSDK_GenerateLanguage_Good_OutputDir(t *testing.T) {
|
|
t.Run("output directory uses language subdirectory", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
|
|
specPath := filepath.Join(tmpDir, "openapi.yaml")
|
|
err := os.WriteFile(specPath, []byte(minimalSpec), 0644)
|
|
require.NoError(t, err)
|
|
|
|
cfg := &Config{
|
|
Spec: "openapi.yaml",
|
|
Languages: []string{"typescript"},
|
|
Output: "custom-sdk",
|
|
Package: PackageConfig{
|
|
Name: "my-client",
|
|
Version: "2.0.0",
|
|
},
|
|
}
|
|
s := New(tmpDir, cfg)
|
|
s.SetVersion("v2.0.0")
|
|
|
|
// This will fail because generators aren't installed, but we can verify
|
|
// the spec detection works correctly
|
|
specResult, err := s.DetectSpec()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, specPath, specResult)
|
|
})
|
|
}
|
|
|
|
func TestSDK_GenerateLanguage_Bad_NoSpec(t *testing.T) {
|
|
t.Run("fails when no OpenAPI spec exists", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
|
|
s := New(tmpDir, &Config{
|
|
Languages: []string{"typescript"},
|
|
Output: "sdk",
|
|
})
|
|
|
|
err := s.GenerateLanguage(context.Background(), "typescript")
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "no OpenAPI spec found")
|
|
})
|
|
}
|
|
|
|
func TestSDK_GenerateLanguage_Bad_UnknownLanguage(t *testing.T) {
|
|
t.Run("fails for unregistered language", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
specPath := filepath.Join(tmpDir, "openapi.yaml")
|
|
err := os.WriteFile(specPath, []byte(minimalSpec), 0644)
|
|
require.NoError(t, err)
|
|
|
|
s := New(tmpDir, nil)
|
|
err = s.GenerateLanguage(context.Background(), "cobol")
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "unknown language: cobol")
|
|
})
|
|
}
|
|
|
|
// --- Generator Registry Tests ---
|
|
|
|
func TestRegistry_Good_RegisterAndGet(t *testing.T) {
|
|
t.Run("register and retrieve all generators", func(t *testing.T) {
|
|
registry := generators.NewRegistry()
|
|
registry.Register(generators.NewTypeScriptGenerator())
|
|
registry.Register(generators.NewPythonGenerator())
|
|
registry.Register(generators.NewGoGenerator())
|
|
registry.Register(generators.NewPHPGenerator())
|
|
|
|
// Verify all languages are registered
|
|
languages := registry.Languages()
|
|
assert.Len(t, languages, 4)
|
|
assert.Contains(t, languages, "typescript")
|
|
assert.Contains(t, languages, "python")
|
|
assert.Contains(t, languages, "go")
|
|
assert.Contains(t, languages, "php")
|
|
|
|
// Verify retrieval
|
|
for _, lang := range []string{"typescript", "python", "go", "php"} {
|
|
gen, ok := registry.Get(lang)
|
|
assert.True(t, ok, "should find generator for %s", lang)
|
|
assert.Equal(t, lang, gen.Language())
|
|
}
|
|
})
|
|
|
|
t.Run("Get returns false for unregistered language", func(t *testing.T) {
|
|
registry := generators.NewRegistry()
|
|
gen, ok := registry.Get("rust")
|
|
assert.False(t, ok)
|
|
assert.Nil(t, gen)
|
|
})
|
|
}
|
|
|
|
func TestRegistry_Good_OverwritesDuplicateLanguage(t *testing.T) {
|
|
registry := generators.NewRegistry()
|
|
registry.Register(generators.NewTypeScriptGenerator())
|
|
registry.Register(generators.NewTypeScriptGenerator()) // register again
|
|
|
|
languages := registry.Languages()
|
|
count := 0
|
|
for _, lang := range languages {
|
|
if lang == "typescript" {
|
|
count++
|
|
}
|
|
}
|
|
assert.Equal(t, 1, count, "should have exactly one typescript entry")
|
|
}
|
|
|
|
// --- Generator Interface Compliance Tests ---
|
|
|
|
func TestGenerators_Good_LanguageIdentifiers(t *testing.T) {
|
|
tests := []struct {
|
|
generator generators.Generator
|
|
expected string
|
|
}{
|
|
{generators.NewTypeScriptGenerator(), "typescript"},
|
|
{generators.NewPythonGenerator(), "python"},
|
|
{generators.NewGoGenerator(), "go"},
|
|
{generators.NewPHPGenerator(), "php"},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.expected, func(t *testing.T) {
|
|
assert.Equal(t, tc.expected, tc.generator.Language())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGenerators_Good_InstallInstructions(t *testing.T) {
|
|
tests := []struct {
|
|
language string
|
|
gen generators.Generator
|
|
contains string
|
|
}{
|
|
{"typescript", generators.NewTypeScriptGenerator(), "npm install"},
|
|
{"python", generators.NewPythonGenerator(), "pip install"},
|
|
{"go", generators.NewGoGenerator(), "go install"},
|
|
{"php", generators.NewPHPGenerator(), "Docker"},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.language, func(t *testing.T) {
|
|
instructions := tc.gen.Install()
|
|
assert.NotEmpty(t, instructions)
|
|
assert.Contains(t, instructions, tc.contains)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGenerators_Good_AvailableDoesNotPanic(t *testing.T) {
|
|
// Available() should never panic regardless of system state
|
|
gens := []generators.Generator{
|
|
generators.NewTypeScriptGenerator(),
|
|
generators.NewPythonGenerator(),
|
|
generators.NewGoGenerator(),
|
|
generators.NewPHPGenerator(),
|
|
}
|
|
|
|
for _, gen := range gens {
|
|
t.Run(gen.Language(), func(t *testing.T) {
|
|
// Should not panic — result depends on system
|
|
_ = gen.Available()
|
|
})
|
|
}
|
|
}
|
|
|
|
// --- SDK Config Tests ---
|
|
|
|
func TestSDKConfig_Good_DefaultConfig(t *testing.T) {
|
|
t.Run("default config has all four languages", func(t *testing.T) {
|
|
cfg := DefaultConfig()
|
|
assert.Contains(t, cfg.Languages, "typescript")
|
|
assert.Contains(t, cfg.Languages, "python")
|
|
assert.Contains(t, cfg.Languages, "go")
|
|
assert.Contains(t, cfg.Languages, "php")
|
|
assert.Len(t, cfg.Languages, 4)
|
|
})
|
|
|
|
t.Run("default config enables diff", func(t *testing.T) {
|
|
cfg := DefaultConfig()
|
|
assert.True(t, cfg.Diff.Enabled)
|
|
assert.False(t, cfg.Diff.FailOnBreaking)
|
|
})
|
|
|
|
t.Run("default config uses sdk/ output", func(t *testing.T) {
|
|
cfg := DefaultConfig()
|
|
assert.Equal(t, "sdk", cfg.Output)
|
|
})
|
|
}
|
|
|
|
func TestSDKConfig_Good_SetVersion(t *testing.T) {
|
|
t.Run("SetVersion updates both fields", func(t *testing.T) {
|
|
s := New("/tmp", &Config{
|
|
Package: PackageConfig{
|
|
Name: "test",
|
|
Version: "old",
|
|
},
|
|
})
|
|
s.SetVersion("v3.0.0")
|
|
|
|
assert.Equal(t, "v3.0.0", s.version)
|
|
assert.Equal(t, "v3.0.0", s.config.Package.Version)
|
|
})
|
|
|
|
t.Run("SetVersion on nil config is safe", func(t *testing.T) {
|
|
s := &SDK{}
|
|
// Should not panic
|
|
s.SetVersion("v1.0.0")
|
|
assert.Equal(t, "v1.0.0", s.version)
|
|
})
|
|
}
|
|
|
|
func TestSDKConfig_Good_NewWithNilConfig(t *testing.T) {
|
|
s := New("/project", nil)
|
|
assert.NotNil(t, s.config)
|
|
assert.Equal(t, "sdk", s.config.Output)
|
|
assert.True(t, s.config.Diff.Enabled)
|
|
}
|
|
|
|
// --- Spec Detection Integration Tests ---
|
|
|
|
func TestSpecDetection_Good_Priority(t *testing.T) {
|
|
t.Run("configured spec takes priority over common paths", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
|
|
// Create both a common path spec and a configured spec
|
|
commonSpec := filepath.Join(tmpDir, "openapi.yaml")
|
|
err := os.WriteFile(commonSpec, []byte(minimalSpec), 0644)
|
|
require.NoError(t, err)
|
|
|
|
configuredSpec := filepath.Join(tmpDir, "custom", "api.yaml")
|
|
require.NoError(t, os.MkdirAll(filepath.Dir(configuredSpec), 0755))
|
|
err = os.WriteFile(configuredSpec, []byte(minimalSpec), 0644)
|
|
require.NoError(t, err)
|
|
|
|
s := New(tmpDir, &Config{Spec: "custom/api.yaml"})
|
|
specPath, err := s.DetectSpec()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, configuredSpec, specPath)
|
|
})
|
|
|
|
t.Run("common paths checked in order", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
|
|
// Create the second common path only (api/openapi.yaml is first)
|
|
apiDir := filepath.Join(tmpDir, "api")
|
|
require.NoError(t, os.MkdirAll(apiDir, 0755))
|
|
apiSpec := filepath.Join(apiDir, "openapi.json")
|
|
err := os.WriteFile(apiSpec, []byte(`{"openapi":"3.0.0"}`), 0644)
|
|
require.NoError(t, err)
|
|
|
|
s := New(tmpDir, nil)
|
|
specPath, err := s.DetectSpec()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, apiSpec, specPath)
|
|
})
|
|
}
|
|
|
|
func TestSpecDetection_Good_AllCommonPaths(t *testing.T) {
|
|
for _, commonPath := range commonSpecPaths {
|
|
t.Run(commonPath, func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
|
|
specPath := filepath.Join(tmpDir, commonPath)
|
|
require.NoError(t, os.MkdirAll(filepath.Dir(specPath), 0755))
|
|
err := os.WriteFile(specPath, []byte(minimalSpec), 0644)
|
|
require.NoError(t, err)
|
|
|
|
s := New(tmpDir, nil)
|
|
detected, err := s.DetectSpec()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, specPath, detected)
|
|
})
|
|
}
|
|
}
|
|
|
|
// --- Compile-time interface checks ---
|
|
|
|
var _ generators.Generator = (*generators.TypeScriptGenerator)(nil)
|
|
var _ generators.Generator = (*generators.PythonGenerator)(nil)
|
|
var _ generators.Generator = (*generators.GoGenerator)(nil)
|
|
var _ generators.Generator = (*generators.PHPGenerator)(nil)
|
|
|
|
// minimalSpec is a valid OpenAPI 3.0 spec used across tests.
|
|
const minimalSpec = `openapi: "3.0.0"
|
|
info:
|
|
title: Test API
|
|
version: "1.0.0"
|
|
paths:
|
|
/health:
|
|
get:
|
|
operationId: getHealth
|
|
responses:
|
|
"200":
|
|
description: OK
|
|
`
|