- Interactive deploy command with 8-step walkthrough: framework → provider → token → SSH → server → inference → tailscale → discord - .env file generation from walkthrough config - DeploymentConfig struct with framework-aware defaults - Inference API client with validation for Venice, OpenRouter, OpenAI, Anthropic - Hetzner Cloud provider: token validation, SSH key listing - DotEnv parser/writer with schema validation - Destroy command with confirmation prompt - Validation subcommand for checking existing .env files - All tests passing, go vet clean
198 lines
No EOL
4.7 KiB
Go
198 lines
No EOL
4.7 KiB
Go
package provider
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/openboatmobile/obm/internal/validation"
|
|
)
|
|
|
|
// mockProvider implements Provider for testing.
|
|
type mockProvider struct {
|
|
BaseProvider
|
|
checks []validation.Check
|
|
}
|
|
|
|
func newMockProvider() *mockProvider {
|
|
return &mockProvider{
|
|
BaseProvider: BaseProvider{
|
|
DisplayName: "Mock Provider",
|
|
Identifier: "mock",
|
|
TokenKey: "MOCK_TOKEN",
|
|
},
|
|
}
|
|
}
|
|
|
|
func (m *mockProvider) Checks(ctx context.Context) []validation.Check {
|
|
return m.checks
|
|
}
|
|
|
|
func TestBaseProviderMethods(t *testing.T) {
|
|
p := newMockProvider()
|
|
|
|
if p.Name() != "mock" {
|
|
t.Errorf("Name() = %q, want %q", p.Name(), "mock")
|
|
}
|
|
if p.ProviderName() != "Mock Provider" {
|
|
t.Errorf("ProviderName() = %q, want %q", p.ProviderName(), "Mock Provider")
|
|
}
|
|
if p.TokenEnvKey() != "MOCK_TOKEN" {
|
|
t.Errorf("TokenEnvKey() = %q, want %q", p.TokenEnvKey(), "MOCK_TOKEN")
|
|
}
|
|
if p.GetToken() != "" {
|
|
t.Errorf("GetToken() = %q, want empty", p.GetToken())
|
|
}
|
|
|
|
p.SetToken("test-token")
|
|
if p.GetToken() != "test-token" {
|
|
t.Errorf("GetToken() = %q, want %q", p.GetToken(), "test-token")
|
|
}
|
|
}
|
|
|
|
func TestBaseProviderValidate(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
t.Run("no_token", func(t *testing.T) {
|
|
p := newMockProvider()
|
|
err := p.Validate(ctx)
|
|
if err == nil {
|
|
t.Error("Validate() should fail when no token is set")
|
|
}
|
|
})
|
|
|
|
t.Run("has_token", func(t *testing.T) {
|
|
p := newMockProvider()
|
|
p.SetToken("test-token")
|
|
err := p.Validate(ctx)
|
|
// BaseProvider.Validate only checks for token presence
|
|
if err != nil {
|
|
t.Errorf("Validate() = %v, want nil", err)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestRegistry(t *testing.T) {
|
|
// Register a mock provider
|
|
Register("mock-test", func() Provider {
|
|
return newMockProvider()
|
|
})
|
|
|
|
// Verify it's registered
|
|
if _, ok := Registry["mock-test"]; !ok {
|
|
t.Error("mock-test provider not registered")
|
|
}
|
|
|
|
// Verify Get works
|
|
p, err := Get("mock-test")
|
|
if err != nil {
|
|
t.Errorf("Get(mock-test) = %v, want nil", err)
|
|
}
|
|
if p.Name() != "mock" {
|
|
t.Errorf("Get(mock-test).Name() = %q, want %q", p.Name(), "mock")
|
|
}
|
|
}
|
|
|
|
func TestGetUnknownProvider(t *testing.T) {
|
|
_, err := Get("nonexistent")
|
|
if err == nil {
|
|
t.Error("Get(nonexistent) should return error")
|
|
}
|
|
}
|
|
|
|
func TestNames(t *testing.T) {
|
|
// Names() should return all registered provider names
|
|
names := Names()
|
|
if len(names) == 0 {
|
|
t.Error("Names() returned empty slice, want at least one provider")
|
|
}
|
|
// Check that our registered provider is in the list
|
|
found := false
|
|
for _, n := range names {
|
|
if n == "mock-test" {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Error("Names() missing mock-test provider")
|
|
}
|
|
}
|
|
|
|
func TestValidateAll(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
t.Run("with_tokens", func(t *testing.T) {
|
|
p1 := newMockProvider()
|
|
p1.SetToken("token1")
|
|
p1.checks = []validation.Check{
|
|
validation.CheckFunc{
|
|
NameField: "token-auth",
|
|
CategoryField: validation.CategoryCredentials,
|
|
RunFunc: func(ctx context.Context) validation.CheckResult {
|
|
return validation.CheckResult{Status: validation.Pass, Message: "Token valid"}
|
|
},
|
|
},
|
|
}
|
|
|
|
p2 := newMockProvider()
|
|
p2.DisplayName = "Second Provider"
|
|
p2.Identifier = "mock2"
|
|
p2.TokenKey = "MOCK2_TOKEN"
|
|
p2.SetToken("token2")
|
|
p2.checks = []validation.Check{
|
|
validation.CheckFunc{
|
|
NameField: "token-auth",
|
|
CategoryField: validation.CategoryCredentials,
|
|
RunFunc: func(ctx context.Context) validation.CheckResult {
|
|
return validation.CheckResult{Status: validation.Pass, Message: "Token valid"}
|
|
},
|
|
},
|
|
}
|
|
|
|
reports, allPassed := ValidateAll(ctx, []Provider{p1, p2})
|
|
|
|
if len(reports) != 2 {
|
|
t.Errorf("ValidateAll() returned %d reports, want 2", len(reports))
|
|
}
|
|
if !allPassed {
|
|
t.Error("ValidateAll() should report all passed")
|
|
}
|
|
})
|
|
|
|
t.Run("with_failures", func(t *testing.T) {
|
|
p := newMockProvider()
|
|
p.SetToken("bad-token")
|
|
p.checks = []validation.Check{
|
|
validation.CheckFunc{
|
|
NameField: "token-auth",
|
|
CategoryField: validation.CategoryCredentials,
|
|
RunFunc: func(ctx context.Context) validation.CheckResult {
|
|
return validation.CheckResult{Status: validation.Fail, Message: "Token rejected by API"}
|
|
},
|
|
},
|
|
}
|
|
|
|
reports, allPassed := ValidateAll(ctx, []Provider{p})
|
|
|
|
if len(reports) != 1 {
|
|
t.Errorf("ValidateAll() returned %d reports, want 1", len(reports))
|
|
}
|
|
if allPassed {
|
|
t.Error("ValidateAll() should report failures")
|
|
}
|
|
if !reports[0].HasFailures() {
|
|
t.Error("Report should have failures")
|
|
}
|
|
})
|
|
|
|
t.Run("skip_no_token", func(t *testing.T) {
|
|
p := newMockProvider()
|
|
// No token set
|
|
|
|
reports, _ := ValidateAll(ctx, []Provider{p})
|
|
|
|
if len(reports) != 0 {
|
|
t.Errorf("ValidateAll() returned %d reports, want 0 (provider has no token)", len(reports))
|
|
}
|
|
})
|
|
} |