Ctrl K
ComunidadGuías…

internal/config/credential_in_flight_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package config

import (
	"bytes"
	"encoding/json"
	"errors"
	"io"
	"math"
	"os"
	"path/filepath"
	"reflect"
	"testing"
	"time"
)

func TestLoadConfigOptionalMissingFallbackAppliesCredentialInFlightDefaults(t *testing.T) {
	cfg, errLoad := LoadConfigOptional(filepath.Join(t.TempDir(), "missing.yaml"), true)
	if errLoad != nil {
		t.Fatalf("LoadConfigOptional() error = %v", errLoad)
	}
	assertOptionalConfigFallback(t, cfg)
}

func TestLoadConfigOptionalEmptyFallbackAppliesCredentialInFlightDefaults(t *testing.T) {
	configPath := filepath.Join(t.TempDir(), "config.yaml")
	if errWrite := os.WriteFile(configPath, nil, 0o600); errWrite != nil {
		t.Fatal(errWrite)
	}
	cfg, errLoad := LoadConfigOptional(configPath, true)
	if errLoad != nil {
		t.Fatalf("LoadConfigOptional() error = %v", errLoad)
	}
	assertOptionalConfigFallback(t, cfg)
}

func TestLoadConfigOptionalWhitespaceFallbackAppliesCredentialInFlightDefaults(t *testing.T) {
	configPath := filepath.Join(t.TempDir(), "config.yaml")
	if errWrite := os.WriteFile(configPath, []byte(" \t\n\r "), 0o600); errWrite != nil {
		t.Fatal(errWrite)
	}
	cfg, errLoad := LoadConfigOptional(configPath, true)
	if errLoad != nil {
		t.Fatalf("LoadConfigOptional() error = %v", errLoad)
	}
	assertOptionalConfigFallback(t, cfg)
}

func TestLoadConfigOptionalInvalidFallbackAppliesCredentialInFlightDefaults(t *testing.T) {
	configPath := filepath.Join(t.TempDir(), "config.yaml")
	if errWrite := os.WriteFile(configPath, []byte(":"), 0o600); errWrite != nil {
		t.Fatal(errWrite)
	}
	cfg, errLoad := LoadConfigOptional(configPath, true)
	if errLoad != nil {
		t.Fatalf("LoadConfigOptional() error = %v", errLoad)
	}
	assertOptionalConfigFallback(t, cfg)
}

func assertOptionalConfigFallback(t *testing.T, cfg *Config) {
	t.Helper()
	if cfg.CredentialInFlight != DefaultCredentialInFlightConfig() {
		t.Fatalf("CredentialInFlight = %#v, want %#v", cfg.CredentialInFlight, DefaultCredentialInFlightConfig())
	}
	if errValidate := cfg.CredentialInFlight.Validate(); errValidate != nil {
		t.Fatalf("CredentialInFlight.Validate() error = %v", errValidate)
	}
	if cfg.ErrorLogsMaxFiles != 0 || cfg.WebsocketAuth || cfg.CredentialConcurrency != (CredentialConcurrencyConfig{}) {
		t.Fatalf("fallback config changed existing empty-config defaults: %#v", cfg)
	}
}

func TestCredentialInFlightConfigContractFixture(t *testing.T) {
	raw, errRead := os.ReadFile(filepath.Join("..", "home", "testdata", "credential_in_flight_contract.json"))
	if errRead != nil {
		t.Fatal(errRead)
	}
	fixture, errDecode := decodeCredentialInFlightConfigFixture(raw)
	if errDecode != nil {
		t.Fatal(errDecode)
	}
	if fixture.Config != DefaultCredentialInFlightConfig() {
		t.Fatalf("default config = %#v, want %#v", DefaultCredentialInFlightConfig(), fixture.Config)
	}
	if errValidate := fixture.Config.Validate(); errValidate != nil {
		t.Fatalf("Validate() error = %v", errValidate)
	}
	assertCredentialInFlightConfigFields(t)
	assertRequiredJSONKeys(t, raw, []string{"config", "part", "overflow"})
	assertRequiredJSONKeys(t, fixture.ConfigJSON, []string{"snapshot-interval", "stale-after", "max-part-bytes", "max-part-count", "max-revision-bytes", "max-aggregate-groups", "max-details", "max-string-bytes", "staging-retention"})
}

func TestCredentialInFlightConfigFixtureRejectsInvalidJSON(t *testing.T) {
	raw, errRead := os.ReadFile(filepath.Join("..", "home", "testdata", "credential_in_flight_contract.json"))
	if errRead != nil {
		t.Fatal(errRead)
	}
	for _, test := range []struct {
		name string
		raw  []byte
	}{
		{name: "unknown config field", raw: bytes.Replace(raw, []byte(`"snapshot-interval": "2s"`), []byte(`"snapshot-interval": "2s", "secret": "secret"`), 1)},
		{name: "trailing JSON", raw: append(append([]byte{}, raw...), []byte(` {"config": {}}`)...)},
	} {
		t.Run(test.name, func(t *testing.T) {
			if _, errDecode := decodeCredentialInFlightConfigFixture(test.raw); errDecode == nil {
				t.Fatal("decodeCredentialInFlightConfigFixture() error = nil")
			}
		})
	}
}

func TestCredentialInFlightConfigDurationBounds(t *testing.T) {
	for _, test := range []struct {
		name  string
		stale string
		every string
		valid bool
	}{
		{name: "exact three intervals", every: "1s", stale: "3s", valid: true},
		{name: "below three intervals", every: "1s", stale: "2999999999ns", valid: false},
		{name: "near duration maximum", every: time.Duration(math.MaxInt64 / 2).String(), stale: time.Duration(math.MaxInt64).String(), valid: false},
	} {
		t.Run(test.name, func(t *testing.T) {
			cfg := DefaultCredentialInFlightConfig()
			cfg.SnapshotInterval = test.every
			cfg.StaleAfter = test.stale
			errValidate := cfg.Validate()
			if (errValidate == nil) != test.valid {
				t.Fatalf("Validate() error = %v, want valid = %t", errValidate, test.valid)
			}
		})
	}
}

func TestCredentialInFlightConfigRejectsUnsafeBounds(t *testing.T) {
	cfg := DefaultCredentialInFlightConfig()
	cfg.StaleAfter = "5s"
	if errValidate := cfg.Validate(); errValidate == nil {
		t.Fatal("Validate() error = nil, want stale-after error")
	}
	cfg = DefaultCredentialInFlightConfig()
	cfg.MaxRevisionBytes = 16*1024*1024 + 1
	if errValidate := cfg.Validate(); errValidate == nil {
		t.Fatal("Validate() error = nil, want hard revision bound error")
	}
	cfg = DefaultCredentialInFlightConfig()
	cfg.MaxPartBytes = math.MaxInt
	if errValidate := cfg.Validate(); errValidate == nil {
		t.Fatal("Validate() error = nil, want overflow-safe part bound error")
	}
}

type credentialInFlightConfigFixture struct {
	Config     CredentialInFlightConfig `json:"config"`
	ConfigJSON json.RawMessage          `json:"-"`
}

func decodeCredentialInFlightConfigFixture(raw []byte) (credentialInFlightConfigFixture, error) {
	var fixture credentialInFlightConfigFixture
	var document struct {
		Config   json.RawMessage `json:"config"`
		Part     json.RawMessage `json:"part"`
		Overflow json.RawMessage `json:"overflow"`
	}
	decoder := json.NewDecoder(bytes.NewReader(raw))
	decoder.DisallowUnknownFields()
	if errDecode := decoder.Decode(&document); errDecode != nil {
		return fixture, errDecode
	}
	if errDecode := decoder.Decode(&struct{}{}); errDecode == nil {
		return fixture, errors.New("unexpected trailing JSON")
	} else if errDecode != io.EOF {
		return fixture, errDecode
	}
	decoder = json.NewDecoder(bytes.NewReader(document.Config))
	decoder.DisallowUnknownFields()
	if errDecode := decoder.Decode(&fixture.Config); errDecode != nil {
		return fixture, errDecode
	}
	if errDecode := decoder.Decode(&struct{}{}); errDecode == nil {
		return fixture, errors.New("unexpected trailing config JSON")
	} else if errDecode != io.EOF {
		return fixture, errDecode
	}
	fixture.ConfigJSON = document.Config
	return fixture, nil
}

func assertCredentialInFlightConfigFields(t *testing.T) {
	t.Helper()
	assertOrderedJSONFields(t, reflect.TypeOf(CredentialInFlightConfig{}), []jsonField{
		{name: "SnapshotInterval", tag: "snapshot-interval"},
		{name: "StaleAfter", tag: "stale-after"},
		{name: "MaxPartBytes", tag: "max-part-bytes"},
		{name: "MaxPartCount", tag: "max-part-count"},
		{name: "MaxRevisionBytes", tag: "max-revision-bytes"},
		{name: "MaxAggregateGroups", tag: "max-aggregate-groups"},
		{name: "MaxDetails", tag: "max-details"},
		{name: "MaxStringBytes", tag: "max-string-bytes"},
		{name: "StagingRetention", tag: "staging-retention"},
	})
}

type jsonField struct {
	name string
	tag  string
}

func assertOrderedJSONFields(t *testing.T, structType reflect.Type, want []jsonField) {
	t.Helper()
	if structType.NumField() != len(want) {
		t.Fatalf("%s field count = %d, want %d", structType.Name(), structType.NumField(), len(want))
	}
	for index, expected := range want {
		field := structType.Field(index)
		if field.Name != expected.name || field.Tag.Get("json") != expected.tag {
			t.Fatalf("%s field %d = (%q, %q), want (%q, %q)", structType.Name(), index, field.Name, field.Tag.Get("json"), expected.name, expected.tag)
		}
	}
}

func assertRequiredJSONKeys(t *testing.T, raw json.RawMessage, required []string) {
	t.Helper()
	var fields map[string]json.RawMessage
	if errDecode := json.Unmarshal(raw, &fields); errDecode != nil {
		t.Fatalf("json.Unmarshal() error = %v", errDecode)
	}
	for _, key := range required {
		if _, ok := fields[key]; !ok {
			t.Fatalf("required JSON key %q is missing", key)
		}
	}
}