Pulse/internal/config/api_tokens_test.go
rcourtman 50843d3013 test: Add LoadAPITokens error path tests
- Test nonexistent file returns empty slice (not error)
- Test empty file returns empty slice
- Test invalid JSON returns error
- Improves LoadAPITokens coverage from 80% to 93.3%
2025-12-01 15:00:56 +00:00

267 lines
7.9 KiB
Go

package config
import (
"os"
"path/filepath"
"testing"
)
func TestAPITokenRecordHasScope(t *testing.T) {
record := APITokenRecord{Scopes: []string{ScopeMonitoringRead}}
if !record.HasScope(ScopeMonitoringRead) {
t.Fatalf("expected scope %q to be granted", ScopeMonitoringRead)
}
if record.HasScope(ScopeSettingsWrite) {
t.Fatalf("did not expect scope %q to be granted", ScopeSettingsWrite)
}
record.Scopes = nil // legacy tokens with no scopes should default to wildcard
if !record.HasScope(ScopeSettingsWrite) {
t.Fatalf("expected wildcard to grant %q", ScopeSettingsWrite)
}
// Empty scope always returns true
record.Scopes = []string{ScopeMonitoringRead}
if !record.HasScope("") {
t.Fatalf("expected empty scope to always be granted")
}
// Explicit wildcard scope in list grants any scope
record.Scopes = []string{ScopeWildcard}
if !record.HasScope(ScopeSettingsWrite) {
t.Fatalf("expected wildcard scope in list to grant %q", ScopeSettingsWrite)
}
if !record.HasScope(ScopeMonitoringRead) {
t.Fatalf("expected wildcard scope in list to grant %q", ScopeMonitoringRead)
}
if !IsKnownScope(ScopeMonitoringRead) {
t.Fatalf("expected %q to be known scope", ScopeMonitoringRead)
}
if IsKnownScope("unknown:scope") {
t.Fatalf("unexpected scope recognised")
}
}
func TestTokenPrefix(t *testing.T) {
tests := []struct {
name string
value string
expected string
}{
{name: "longer than 6 chars", value: "abcdefghij", expected: "abcdef"},
{name: "exactly 6 chars", value: "abcdef", expected: "abcdef"},
{name: "shorter than 6 chars", value: "abc", expected: "abc"},
{name: "empty string", value: "", expected: ""},
{name: "single char", value: "x", expected: "x"},
{name: "5 chars", value: "12345", expected: "12345"},
{name: "7 chars", value: "1234567", expected: "123456"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := tokenPrefix(tt.value)
if result != tt.expected {
t.Errorf("tokenPrefix(%q) = %q, want %q", tt.value, result, tt.expected)
}
})
}
}
func TestTokenSuffix(t *testing.T) {
tests := []struct {
name string
value string
expected string
}{
{name: "longer than 4 chars", value: "abcdefghij", expected: "ghij"},
{name: "exactly 4 chars", value: "abcd", expected: "abcd"},
{name: "shorter than 4 chars", value: "abc", expected: "abc"},
{name: "empty string", value: "", expected: ""},
{name: "single char", value: "x", expected: "x"},
{name: "3 chars", value: "123", expected: "123"},
{name: "5 chars", value: "12345", expected: "2345"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := tokenSuffix(tt.value)
if result != tt.expected {
t.Errorf("tokenSuffix(%q) = %q, want %q", tt.value, result, tt.expected)
}
})
}
}
func TestNormalizeScopes(t *testing.T) {
tests := []struct {
name string
scopes []string
expected []string
}{
{
name: "nil returns wildcard",
scopes: nil,
expected: []string{ScopeWildcard},
},
{
name: "empty returns wildcard",
scopes: []string{},
expected: []string{ScopeWildcard},
},
{
name: "single scope preserved",
scopes: []string{ScopeMonitoringRead},
expected: []string{ScopeMonitoringRead},
},
{
name: "multiple scopes preserved",
scopes: []string{ScopeMonitoringRead, ScopeSettingsWrite},
expected: []string{ScopeMonitoringRead, ScopeSettingsWrite},
},
{
name: "wildcard alone preserved",
scopes: []string{ScopeWildcard},
expected: []string{ScopeWildcard},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := normalizeScopes(tt.scopes)
if len(result) != len(tt.expected) {
t.Fatalf("normalizeScopes(%v) length = %d, want %d", tt.scopes, len(result), len(tt.expected))
}
for i, v := range result {
if v != tt.expected[i] {
t.Errorf("normalizeScopes(%v)[%d] = %q, want %q", tt.scopes, i, v, tt.expected[i])
}
}
})
}
}
func TestNormalizeScopes_ReturnsCopy(t *testing.T) {
original := []string{ScopeMonitoringRead, ScopeSettingsWrite}
result := normalizeScopes(original)
// Modify result and verify original is unchanged
result[0] = "modified"
if original[0] != ScopeMonitoringRead {
t.Errorf("normalizeScopes did not return a copy; original was modified")
}
}
func TestIsKnownScope(t *testing.T) {
tests := []struct {
name string
scope string
expected bool
}{
{name: "wildcard", scope: ScopeWildcard, expected: true},
{name: "monitoring:read", scope: ScopeMonitoringRead, expected: true},
{name: "monitoring:write", scope: ScopeMonitoringWrite, expected: true},
{name: "docker:report", scope: ScopeDockerReport, expected: true},
{name: "docker:manage", scope: ScopeDockerManage, expected: true},
{name: "host-agent:report", scope: ScopeHostReport, expected: true},
{name: "host-agent:manage", scope: ScopeHostManage, expected: true},
{name: "settings:read", scope: ScopeSettingsRead, expected: true},
{name: "settings:write", scope: ScopeSettingsWrite, expected: true},
{name: "unknown scope", scope: "unknown:scope", expected: false},
{name: "empty string", scope: "", expected: false},
{name: "partial match", scope: "monitoring", expected: false},
{name: "case sensitive", scope: "MONITORING:READ", expected: false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := IsKnownScope(tt.scope)
if result != tt.expected {
t.Errorf("IsKnownScope(%q) = %v, want %v", tt.scope, result, tt.expected)
}
})
}
}
func TestLoadAPITokensAppliesLegacyScopes(t *testing.T) {
if len(AllKnownScopes) == 0 {
t.Fatal("expected known scopes to be defined")
}
dir := t.TempDir()
persistence := NewConfigPersistence(dir)
if err := persistence.EnsureConfigDir(); err != nil {
t.Fatalf("EnsureConfigDir: %v", err)
}
payload := `[{"id":"legacy","name":"legacy","hash":"abc","createdAt":"2024-01-01T00:00:00Z"}]`
if err := os.WriteFile(filepath.Join(dir, "api_tokens.json"), []byte(payload), 0600); err != nil {
t.Fatalf("write api_tokens.json: %v", err)
}
tokens, err := persistence.LoadAPITokens()
if err != nil {
t.Fatalf("LoadAPITokens: %v", err)
}
if len(tokens) != 1 {
t.Fatalf("expected 1 token, got %d", len(tokens))
}
if len(tokens[0].Scopes) != 1 || tokens[0].Scopes[0] != ScopeWildcard {
t.Fatalf("expected legacy token to default to wildcard scope, got %#v", tokens[0].Scopes)
}
}
func TestLoadAPITokens_ErrorPaths(t *testing.T) {
t.Run("nonexistent file returns empty slice", func(t *testing.T) {
dir := t.TempDir()
persistence := NewConfigPersistence(dir)
tokens, err := persistence.LoadAPITokens()
if err != nil {
t.Fatalf("expected no error for missing file, got: %v", err)
}
if len(tokens) != 0 {
t.Fatalf("expected empty slice, got %d tokens", len(tokens))
}
})
t.Run("empty file returns empty slice", func(t *testing.T) {
dir := t.TempDir()
persistence := NewConfigPersistence(dir)
if err := persistence.EnsureConfigDir(); err != nil {
t.Fatalf("EnsureConfigDir: %v", err)
}
// Write empty file
if err := os.WriteFile(filepath.Join(dir, "api_tokens.json"), []byte{}, 0600); err != nil {
t.Fatalf("write file: %v", err)
}
tokens, err := persistence.LoadAPITokens()
if err != nil {
t.Fatalf("expected no error for empty file, got: %v", err)
}
if len(tokens) != 0 {
t.Fatalf("expected empty slice, got %d tokens", len(tokens))
}
})
t.Run("invalid JSON returns error", func(t *testing.T) {
dir := t.TempDir()
persistence := NewConfigPersistence(dir)
if err := persistence.EnsureConfigDir(); err != nil {
t.Fatalf("EnsureConfigDir: %v", err)
}
// Write invalid JSON
if err := os.WriteFile(filepath.Join(dir, "api_tokens.json"), []byte("not valid json"), 0600); err != nil {
t.Fatalf("write file: %v", err)
}
_, err := persistence.LoadAPITokens()
if err == nil {
t.Fatal("expected error for invalid JSON, got nil")
}
})
}