package identity import ( "context" "encoding/json" "net/http" "net/http/httptest" "testing" "time" "github.com/google/uuid" ) func TestTenantContextClientUsesApplicationMachineTokenAndETag(t *testing.T) { tenantID, applicationID := uuid.NewString(), uuid.NewString() var issuer string server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/issuer/shared/.well-known/openid-configuration": _ = json.NewEncoder(w).Encode(map[string]any{ "issuer": issuer, "token_endpoint": serverURL(r) + "/issuer/shared/token", }) case "/issuer/shared/token": clientID, secret, ok := r.BasicAuth() if !ok || clientID != "gateway-machine" || secret != "machine-secret-material" || r.FormValue("grant_type") != "client_credentials" || r.FormValue("scope") != "tenant.context.read" { t.Fatal("tenant context token request was not least-privilege client credentials") } _ = json.NewEncoder(w).Encode(map[string]any{ "access_token": "opaque-machine-token", "token_type": "Bearer", "expires_in": 300, }) case "/api/v1/runtime/tenants/" + tenantID: if r.Header.Get("Authorization") != "Bearer opaque-machine-token" { t.Fatal("tenant context request omitted the machine token") } if r.Header.Get("If-None-Match") == `"tenant-v7"` { w.WriteHeader(http.StatusNotModified) return } w.Header().Set("ETag", `"tenant-v7"`) _ = json.NewEncoder(w).Encode(TenantContext{ ApplicationID: applicationID, TenantID: tenantID, DisplayName: "Tenant A", Slug: "tenant-a", TenantStatus: "active", TenantApplicationStatus: "active", Version: "7", UpdatedAt: time.Unix(1_780_000_000, 0).UTC(), }) default: http.NotFound(w, r) } })) defer server.Close() issuer = server.URL + "/issuer/shared" client, err := NewTenantContextClient( "test", issuer, applicationID, server.URL+"/api/v1/runtime/tenants/{tenantId}", "urn:easyai:auth-center:tenant-context", "tenant.context.read", func(context.Context) (string, []byte, error) { return "gateway-machine", []byte("machine-secret-material"), nil }, server.Client(), ) if err != nil { t.Fatal(err) } tenant, unchanged, err := client.Get(context.Background(), tenantID, "") if err != nil || unchanged || tenant.ETag != `"tenant-v7"` || !tenant.Active() { t.Fatalf("tenant=%#v unchanged=%t error=%v", tenant, unchanged, err) } _, unchanged, err = client.Get(context.Background(), tenantID, tenant.ETag) if err != nil || !unchanged { t.Fatalf("etag request unchanged=%t error=%v", unchanged, err) } } func TestTenantContextClientDistinguishesNotFoundFromTemporaryFailure(t *testing.T) { tenantID, applicationID := uuid.NewString(), uuid.NewString() for _, test := range []struct { status int want error }{ {status: http.StatusNotFound, want: ErrTenantContextNotFound}, {status: http.StatusServiceUnavailable, want: ErrTenantContextUnavailable}, } { t.Run(http.StatusText(test.status), func(t *testing.T) { var issuer string server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch { case r.URL.Path == "/issuer/shared/.well-known/openid-configuration": _ = json.NewEncoder(w).Encode(map[string]any{ "issuer": issuer, "token_endpoint": serverURL(r) + "/issuer/shared/token", }) case r.URL.Path == "/issuer/shared/token": _ = json.NewEncoder(w).Encode(map[string]any{ "access_token": "opaque-machine-token", "token_type": "Bearer", "expires_in": 300, }) default: w.WriteHeader(test.status) } })) defer server.Close() issuer = server.URL + "/issuer/shared" client, err := NewTenantContextClient( "test", issuer, applicationID, server.URL+"/api/v1/runtime/tenants/{tenantId}", "urn:easyai:auth-center:tenant-context", "tenant.context.read", func(context.Context) (string, []byte, error) { return "gateway-machine", []byte("machine-secret-material"), nil }, server.Client(), ) if err != nil { t.Fatal(err) } if _, _, err := client.Get(context.Background(), tenantID, ""); err != test.want { t.Fatalf("error=%v want=%v", err, test.want) } }) } } func serverURL(request *http.Request) string { return "http://" + request.Host }