Ctrl K
ComunidadGuías…

internal/registry/codex_client_models_test.go

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

Archivo original ↗Preguntar a KiBot
package registry

import (
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"
)

func TestEmbeddedCodexClientModelsCatalogIsValid(t *testing.T) {
	data, revision := GetCodexClientModelsSnapshot()
	if revision == 0 {
		t.Fatal("embedded Codex client model catalog revision = 0, want non-zero")
	}
	if err := ValidateCodexClientModelsJSON(data); err != nil {
		t.Fatalf("embedded Codex client model catalog is invalid: %v", err)
	}

	data[0] ^= 0xff
	second, secondRevision := GetCodexClientModelsSnapshot()
	if secondRevision != revision {
		t.Fatalf("snapshot revision = %d, want %d", secondRevision, revision)
	}
	if err := ValidateCodexClientModelsJSON(second); err != nil {
		t.Fatalf("mutating returned snapshot changed stored catalog: %v", err)
	}
}

func TestValidateCodexClientModelsJSON(t *testing.T) {
	validDefault := testCodexClientModel("gpt-5.5", 1)
	validOther := testCodexClientModel("gpt-5.6-sol", 2)
	emptySlug := testCodexClientModel("gpt-5.5", 1)
	emptySlug["slug"] = ""
	missingField := testCodexClientModel("gpt-5.5", 1)
	delete(missingField, "base_instructions")
	wrongFieldType := testCodexClientModel("gpt-5.5", 1)
	wrongFieldType["context_window"] = "372000"
	unsupportedDefault := testCodexClientModel("gpt-5.5", 1)
	unsupportedDefault["default_reasoning_level"] = "high"

	tests := []struct {
		name string
		raw  []byte
	}{
		{name: "malformed", raw: []byte(`{"models":`)},
		{name: "empty", raw: []byte(`{"models":[]}`)},
		{name: "empty slug", raw: testCodexClientCatalog(t, emptySlug)},
		{name: "duplicate slug", raw: testCodexClientCatalog(t, validDefault, validDefault)},
		{name: "missing required field", raw: testCodexClientCatalog(t, missingField)},
		{name: "wrong required field type", raw: testCodexClientCatalog(t, wrongFieldType)},
		{name: "default reasoning level not supported", raw: testCodexClientCatalog(t, unsupportedDefault)},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if err := ValidateCodexClientModelsJSON(tt.raw); err == nil {
				t.Fatal("ValidateCodexClientModelsJSON() error = nil, want error")
			}
		})
	}

	valid := testCodexClientCatalog(t, validDefault, validOther)
	if err := ValidateCodexClientModelsJSON(valid); err != nil {
		t.Fatalf("valid catalog rejected: %v", err)
	}
	withoutGPT55 := testCodexClientCatalog(t, validOther)
	if err := ValidateCodexClientModelsJSON(withoutGPT55); err != nil {
		t.Fatalf("valid catalog without gpt-5.5 rejected: %v", err)
	}
}

func TestLoadCodexClientModelsRejectsInvalidWithoutReplacing(t *testing.T) {
	original, _ := GetCodexClientModelsSnapshot()
	t.Cleanup(func() {
		if _, err := loadCodexClientModelsFromBytes(original, "test cleanup"); err != nil {
			t.Fatalf("restore original catalog: %v", err)
		}
	})

	valid := testCodexClientCatalog(t, testCodexClientModel("gpt-5.5", 1))
	changed, err := loadCodexClientModelsFromBytes(valid, "test")
	if err != nil {
		t.Fatalf("load valid catalog: %v", err)
	}
	if !changed {
		t.Fatal("load valid catalog changed = false, want true")
	}
	beforeInvalid, revision := GetCodexClientModelsSnapshot()

	if _, err = loadCodexClientModelsFromBytes([]byte(`{"models":[]}`), "test invalid"); err == nil {
		t.Fatal("load invalid catalog error = nil, want error")
	}
	afterInvalid, afterRevision := GetCodexClientModelsSnapshot()
	if string(afterInvalid) != string(beforeInvalid) {
		t.Fatal("invalid catalog replaced current snapshot")
	}
	if afterRevision != revision {
		t.Fatalf("revision after invalid catalog = %d, want %d", afterRevision, revision)
	}
}

func TestFetchCodexClientModelsFallsBackToNextURL(t *testing.T) {
	invalidServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		_, _ = w.Write([]byte(`{"models":[{"slug":"gpt-5.6-sol"}]}`))
	}))
	defer invalidServer.Close()

	validCatalog := testCodexClientCatalog(t, testCodexClientModel("gpt-5.5", 1))
	validServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodGet {
			t.Errorf("method = %s, want GET", r.Method)
		}
		w.Header().Set("Content-Type", "application/json")
		_, _ = w.Write(validCatalog)
	}))
	defer validServer.Close()

	previousURLs := codexClientModelsURLs
	codexClientModelsURLs = []string{invalidServer.URL, validServer.URL}
	t.Cleanup(func() { codexClientModelsURLs = previousURLs })

	data, sourceURL := fetchCodexClientModelsFromRemote(context.Background())
	if sourceURL != validServer.URL {
		t.Fatalf("source URL = %q, want %q", sourceURL, validServer.URL)
	}
	if string(data) != string(validCatalog) {
		t.Fatalf("catalog = %s, want %s", data, validCatalog)
	}
}

func TestRefreshCodexClientModelsKeepsLastValidSnapshot(t *testing.T) {
	original, _ := GetCodexClientModelsSnapshot()
	previousURLs := codexClientModelsURLs
	t.Cleanup(func() {
		codexClientModelsURLs = previousURLs
		if _, err := loadCodexClientModelsFromBytes(original, "test cleanup"); err != nil {
			t.Fatalf("restore original catalog: %v", err)
		}
	})

	lastValid := testCodexClientCatalog(t, testCodexClientModel("gpt-5.5", 1))
	if _, err := loadCodexClientModelsFromBytes(lastValid, "test last valid"); err != nil {
		t.Fatalf("load last valid catalog: %v", err)
	}

	tests := []struct {
		name       string
		statusCode int
		body       string
	}{
		{name: "remote files missing", statusCode: http.StatusNotFound},
		{name: "remote JSON malformed", statusCode: http.StatusOK, body: `{"models":`},
		{name: "remote JSON incomplete", statusCode: http.StatusOK, body: `{"models":[{"slug":"gpt-5.5"}]}`},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			servers := make([]*httptest.Server, 0, 2)
			urls := make([]string, 0, 2)
			for range 2 {
				server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
					w.WriteHeader(tt.statusCode)
					_, _ = w.Write([]byte(tt.body))
				}))
				servers = append(servers, server)
				urls = append(urls, server.URL)
			}
			defer func() {
				for _, server := range servers {
					server.Close()
				}
			}()

			before, revision := GetCodexClientModelsSnapshot()
			codexClientModelsURLs = urls
			tryRefreshCodexClientModels(context.Background(), "test refresh")
			after, afterRevision := GetCodexClientModelsSnapshot()
			if string(after) != string(before) {
				t.Fatal("failed remote refresh replaced last valid catalog")
			}
			if afterRevision != revision {
				t.Fatalf("revision after failed refresh = %d, want %d", afterRevision, revision)
			}
		})
	}
}

func testCodexClientModel(slug string, priority int) map[string]any {
	return map[string]any{
		"slug":                       slug,
		"display_name":               "Test " + slug,
		"description":                "Test model",
		"base_instructions":          "Test instructions",
		"minimal_client_version":     "0.144.0",
		"visibility":                 "list",
		"context_window":             372000,
		"max_context_window":         372000,
		"priority":                   priority,
		"default_reasoning_level":    "medium",
		"supported_reasoning_levels": []map[string]any{{"effort": "medium", "description": "Balanced"}},
	}
}

func testCodexClientCatalog(t *testing.T, models ...map[string]any) []byte {
	t.Helper()
	data, err := json.Marshal(map[string]any{"models": models})
	if err != nil {
		t.Fatalf("marshal test Codex client catalog: %v", err)
	}
	return data
}