package service import ( "context" "encoding/json" "net/http" "testing" "github.com/stretchr/testify/assert" ) func TestKetoService_CheckPermission(t *testing.T) { handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, "/relation-tuples/check", r.URL.Path) assert.Equal(t, "user1", r.URL.Query().Get("subject_id")) assert.Equal(t, "tenants", r.URL.Query().Get("namespace")) assert.Equal(t, "tenant1", r.URL.Query().Get("object")) assert.Equal(t, "admin", r.URL.Query().Get("relation")) w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(checkResponse{Allowed: true}) }) s := &ketoService{ readURL: "http://keto-read.local", client: clientForHandler(handler), } allowed, err := s.CheckPermission(context.Background(), "user1", "tenants", "tenant1", "admin") assert.NoError(t, err) assert.True(t, allowed) } func TestKetoService_CreateRelation(t *testing.T) { handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, "/admin/relation-tuples", r.URL.Path) assert.Equal(t, "PUT", r.Method) var body map[string]any _ = json.NewDecoder(r.Body).Decode(&body) assert.Equal(t, "tenants", body["namespace"]) assert.Equal(t, "tenant1", body["object"]) assert.Equal(t, "admin", body["relation"]) assert.Equal(t, "user1", body["subject_id"]) w.WriteHeader(http.StatusCreated) }) s := &ketoService{ writeURL: "http://keto-write.local", client: clientForHandler(handler), } err := s.CreateRelation(context.Background(), "tenants", "tenant1", "admin", "user1") assert.NoError(t, err) } func TestKetoService_DeleteRelation(t *testing.T) { handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, "/admin/relation-tuples", r.URL.Path) assert.Equal(t, "DELETE", r.Method) assert.Equal(t, "user1", r.URL.Query().Get("subject_id")) assert.Equal(t, "tenants", r.URL.Query().Get("namespace")) assert.Equal(t, "tenant1", r.URL.Query().Get("object")) assert.Equal(t, "admin", r.URL.Query().Get("relation")) w.WriteHeader(http.StatusNoContent) }) s := &ketoService{ writeURL: "http://keto-write.local", client: clientForHandler(handler), } err := s.DeleteRelation(context.Background(), "tenants", "tenant1", "admin", "user1") assert.NoError(t, err) } func TestKetoService_ListRelations(t *testing.T) { tuples := []RelationTuple{ {Namespace: "tenants", Object: "tenant1", Relation: "admin", SubjectID: "user1"}, } handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, "/relation-tuples", r.URL.Path) w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(relationTuplesResponse{RelationTuples: tuples}) }) s := &ketoService{ readURL: "http://keto-read.local", client: clientForHandler(handler), } result, err := s.ListRelations(context.Background(), "tenants", "tenant1", "admin", "user1") assert.NoError(t, err) assert.Equal(t, tuples, result) } func TestKetoService_ErrorHandling(t *testing.T) { handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) _, _ = w.Write([]byte("internal error")) }) s := &ketoService{ readURL: "http://keto-read.local", writeURL: "http://keto-write.local", client: clientForHandler(handler), } _, err := s.CheckPermission(context.Background(), "u", "n", "o", "r") assert.Error(t, err) err = s.DeleteRelation(context.Background(), "n", "o", "r", "s") assert.Error(t, err) _, err = s.ListRelations(context.Background(), "n", "o", "r", "s") assert.Error(t, err) } func TestKetoService_CheckPermission_Forbidden(t *testing.T) { handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusForbidden) }) s := &ketoService{ readURL: "http://keto-read.local", client: clientForHandler(handler), } allowed, err := s.CheckPermission(context.Background(), "u", "n", "o", "r") assert.NoError(t, err) assert.False(t, allowed) } func TestKetoService_CreateRelation_Retry(t *testing.T) { attempts := 0 handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { attempts++ if attempts < 2 { w.WriteHeader(http.StatusInternalServerError) return } w.WriteHeader(http.StatusCreated) }) s := &ketoService{ writeURL: "http://keto-write.local", client: clientForHandler(handler), } err := s.CreateRelation(context.Background(), "n", "o", "r", "s") assert.NoError(t, err) assert.Equal(t, 2, attempts) }