package forgejo import ( "context" "encoding/json" "net/http" "net/http/httptest" "strings" "testing" ) func TestCreateForgejoUser_Success(t *testing.T) { mux := http.NewServeMux() mux.HandleFunc("POST /api/v1/admin/users", func(w http.ResponseWriter, r *http.Request) { user, pass, ok := r.BasicAuth() if !ok || user != "admin" || pass != "secret" { w.WriteHeader(http.StatusUnauthorized) return } var body map[string]interface{} if err := json.NewDecoder(r.Body).Decode(&body); err != nil { t.Fatalf("decode body: %v", err) } if body["username"] != "alice" { t.Fatalf("expected username 'alice', got %v", body["username"]) } if body["must_change_password"] != false { t.Fatal("expected must_change_password false") } w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode(map[string]interface{}{"id": 2, "login": "alice"}) }) mux.HandleFunc("POST /api/v1/users/alice/tokens", func(w http.ResponseWriter, r *http.Request) { user, _, ok := r.BasicAuth() if !ok || user != "alice" { w.WriteHeader(http.StatusUnauthorized) return } var body map[string]interface{} if err := json.NewDecoder(r.Body).Decode(&body); err != nil { t.Fatalf("decode body: %v", err) } if body["name"] != "dev-pod-access" { t.Fatalf("expected token name 'dev-pod-access', got %v", body["name"]) } w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode(map[string]interface{}{"sha1": "tok_abc123"}) }) srv := httptest.NewServer(mux) defer srv.Close() client := NewClient(srv.URL, "admin", "secret") token, err := client.CreateForgejoUser(context.Background(), "alice") if err != nil { t.Fatalf("CreateForgejoUser: %v", err) } if token != "tok_abc123" { t.Fatalf("expected token 'tok_abc123', got %q", token) } } func TestCreateForgejoUser_CreateUserFails(t *testing.T) { mux := http.NewServeMux() mux.HandleFunc("POST /api/v1/admin/users", func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusUnprocessableEntity) w.Write([]byte(`{"message": "user already exists"}`)) }) srv := httptest.NewServer(mux) defer srv.Close() client := NewClient(srv.URL, "admin", "secret") _, err := client.CreateForgejoUser(context.Background(), "alice") if err == nil { t.Fatal("expected error") } if !strings.Contains(err.Error(), "user already exists") { t.Fatalf("expected 'user already exists' in error, got: %v", err) } } func TestCreateForgejoUser_TokenCreationFails_CleansUpUser(t *testing.T) { var isUserDeleted bool mux := http.NewServeMux() mux.HandleFunc("POST /api/v1/admin/users", func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode(map[string]interface{}{"id": 2, "login": "alice"}) }) mux.HandleFunc("POST /api/v1/users/alice/tokens", func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(`{"message": "db error"}`)) }) mux.HandleFunc("DELETE /api/v1/admin/users/alice", func(w http.ResponseWriter, _ *http.Request) { isUserDeleted = true w.WriteHeader(http.StatusNoContent) }) srv := httptest.NewServer(mux) defer srv.Close() client := NewClient(srv.URL, "admin", "secret") _, err := client.CreateForgejoUser(context.Background(), "alice") if err == nil { t.Fatal("expected error on token creation failure") } if !isUserDeleted { t.Fatal("expected user to be cleaned up after token creation failure") } } func TestDeleteForgejoUser_Success(t *testing.T) { mux := http.NewServeMux() mux.HandleFunc("DELETE /api/v1/admin/users/alice", func(w http.ResponseWriter, r *http.Request) { user, pass, ok := r.BasicAuth() if !ok || user != "admin" || pass != "secret" { w.WriteHeader(http.StatusUnauthorized) return } if r.URL.Query().Get("purge") != "true" { t.Fatal("expected purge=true query param") } w.WriteHeader(http.StatusNoContent) }) srv := httptest.NewServer(mux) defer srv.Close() client := NewClient(srv.URL, "admin", "secret") if err := client.DeleteForgejoUser(context.Background(), "alice"); err != nil { t.Fatalf("DeleteForgejoUser: %v", err) } } func TestDeleteForgejoUser_NotFound_NoError(t *testing.T) { mux := http.NewServeMux() mux.HandleFunc("DELETE /api/v1/admin/users/alice", func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusNotFound) }) srv := httptest.NewServer(mux) defer srv.Close() client := NewClient(srv.URL, "admin", "secret") if err := client.DeleteForgejoUser(context.Background(), "alice"); err != nil { t.Fatalf("expected no error for 404, got: %v", err) } } func TestGetRepoFileContent_Success(t *testing.T) { mux := http.NewServeMux() mux.HandleFunc("GET /api/v1/repos/alice/myrepo/contents/.spinoff.yml", func(w http.ResponseWriter, r *http.Request) { user, pass, ok := r.BasicAuth() if !ok || user != "admin" || pass != "secret" { w.WriteHeader(http.StatusUnauthorized) return } if r.URL.Query().Get("ref") != "main" { t.Fatalf("expected ref=main, got %s", r.URL.Query().Get("ref")) } w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(map[string]interface{}{ "content": "dG9vbHM6IFtydXN0XQo=", "encoding": "base64", }) }) srv := httptest.NewServer(mux) defer srv.Close() client := NewClient(srv.URL, "admin", "secret") content, err := client.GetRepoFileContent(context.Background(), "alice", "myrepo", ".spinoff.yml", "main") if err != nil { t.Fatalf("GetRepoFileContent: %v", err) } if string(content) != "tools: [rust]\n" { t.Fatalf("expected 'tools: [rust]\\n', got %q", string(content)) } } func TestGetRepoFileContent_NotFound(t *testing.T) { mux := http.NewServeMux() mux.HandleFunc("GET /api/v1/repos/alice/myrepo/contents/.spinoff.yml", func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusNotFound) }) srv := httptest.NewServer(mux) defer srv.Close() client := NewClient(srv.URL, "admin", "secret") content, err := client.GetRepoFileContent(context.Background(), "alice", "myrepo", ".spinoff.yml", "main") if err != nil { t.Fatalf("expected no error for 404, got: %v", err) } if content != nil { t.Fatalf("expected nil content for 404, got %q", string(content)) } } func TestCreateIssueComment_Success(t *testing.T) { mux := http.NewServeMux() mux.HandleFunc("POST /api/v1/repos/alice/myrepo/issues/42/comments", func(w http.ResponseWriter, r *http.Request) { user, pass, ok := r.BasicAuth() if !ok || user != "admin" || pass != "secret" { w.WriteHeader(http.StatusUnauthorized) return } var body map[string]string if err := json.NewDecoder(r.Body).Decode(&body); err != nil { t.Fatalf("decode body: %v", err) } if body["body"] != "Builder pod created" { t.Fatalf("expected body 'Builder pod created', got %q", body["body"]) } w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode(map[string]interface{}{"id": 1}) }) srv := httptest.NewServer(mux) defer srv.Close() client := NewClient(srv.URL, "admin", "secret") err := client.CreateIssueComment(context.Background(), "alice", "myrepo", 42, "Builder pod created") if err != nil { t.Fatalf("CreateIssueComment: %v", err) } } func TestCreateIssueComment_Failure(t *testing.T) { mux := http.NewServeMux() mux.HandleFunc("POST /api/v1/repos/alice/myrepo/issues/42/comments", func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusForbidden) w.Write([]byte(`{"message": "forbidden"}`)) }) srv := httptest.NewServer(mux) defer srv.Close() client := NewClient(srv.URL, "admin", "secret") err := client.CreateIssueComment(context.Background(), "alice", "myrepo", 42, "test") if err == nil { t.Fatal("expected error") } if !strings.Contains(err.Error(), "403") { t.Fatalf("expected 403 in error, got: %v", err) } } func TestGeneratePassword(t *testing.T) { p1, err := generatePassword(16) if err != nil { t.Fatalf("generatePassword: %v", err) } if len(p1) != 32 { t.Fatalf("expected 32 hex chars, got %d", len(p1)) } p2, _ := generatePassword(16) if p1 == p2 { t.Fatal("expected different passwords") } }