dev-pod-api-build/internal/forgejo/client_test.go
2026-04-16 04:16:36 +00:00

269 lines
7.9 KiB
Go

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")
}
}