package gitea_test import ( "context" "encoding/json" "io" "net/http" "net/http/httptest" "strings" "testing" "github.com/mathiasbq/hyperguild/ingestion/internal/gitea" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) const testToken = "super-secret-token-value" func TestNewNilWhenUnconfigured(t *testing.T) { assert.Nil(t, gitea.New("", testToken)) assert.Nil(t, gitea.New("https://git.example", "")) } func TestCreateIssueForcesOwnerAndAuth(t *testing.T) { var gotPath, gotAuth, gotBody string srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gotPath = r.URL.Path gotAuth = r.Header.Get("Authorization") b, _ := io.ReadAll(r.Body) gotBody = string(b) assert.Equal(t, http.MethodPost, r.Method) w.WriteHeader(http.StatusCreated) _ = json.NewEncoder(w).Encode(map[string]any{"number": 42, "html_url": "https://git.d-ma.be/mathias/hyperguild/issues/42"}) })) defer srv.Close() c := gitea.New(srv.URL, testToken) require.NotNil(t, c) ref, err := c.CreateIssue(context.Background(), "hyperguild", "Do the thing", "details") require.NoError(t, err) assert.Equal(t, "/api/v1/repos/mathias/hyperguild/issues", gotPath, "owner forced to mathias") assert.Equal(t, "token "+testToken, gotAuth) assert.Contains(t, gotBody, "Do the thing") assert.Equal(t, "hyperguild", ref.Repo) assert.Equal(t, 42, ref.Number) assert.Contains(t, ref.URL, "/issues/42") } func TestCommentIssue(t *testing.T) { var gotPath string srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gotPath = r.URL.Path w.WriteHeader(http.StatusCreated) _ = json.NewEncoder(w).Encode(map[string]any{"html_url": "https://git/c/1"}) })) defer srv.Close() ref, err := gitea.New(srv.URL, testToken).CommentIssue(context.Background(), "hyperguild", 7, "a comment") require.NoError(t, err) assert.Equal(t, "/api/v1/repos/mathias/hyperguild/issues/7/comments", gotPath) assert.Equal(t, 7, ref.Number) } func TestCloseIssueWithComment(t *testing.T) { var paths []string var states []string srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { paths = append(paths, r.Method+" "+r.URL.Path) if r.Method == http.MethodPatch { var body map[string]any b, _ := io.ReadAll(r.Body) _ = json.Unmarshal(b, &body) states = append(states, body["state"].(string)) } w.WriteHeader(http.StatusOK) _ = json.NewEncoder(w).Encode(map[string]any{"number": 9, "html_url": "https://git/i/9"}) })) defer srv.Close() ref, err := gitea.New(srv.URL, testToken).CloseIssue(context.Background(), "hyperguild", 9, "closing because done") require.NoError(t, err) assert.Equal(t, 9, ref.Number) // Comment posted first, then state PATCHed to closed. assert.Contains(t, paths, "POST /api/v1/repos/mathias/hyperguild/issues/9/comments") assert.Contains(t, paths, "PATCH /api/v1/repos/mathias/hyperguild/issues/9") assert.Equal(t, []string{"closed"}, states) } func TestCloseIssueNoComment(t *testing.T) { var commented bool srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if strings.HasSuffix(r.URL.Path, "/comments") { commented = true } w.WriteHeader(http.StatusOK) _ = json.NewEncoder(w).Encode(map[string]any{"number": 3, "html_url": "https://git/i/3"}) })) defer srv.Close() _, err := gitea.New(srv.URL, testToken).CloseIssue(context.Background(), "hyperguild", 3, "") require.NoError(t, err) assert.False(t, commented, "empty comment ⇒ no comment POST") } func TestErrorPathDoesNotLeakToken(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusInternalServerError) _, _ = w.Write([]byte("boom")) })) defer srv.Close() _, err := gitea.New(srv.URL, testToken).CreateIssue(context.Background(), "hyperguild", "t", "b") require.Error(t, err) assert.NotContains(t, err.Error(), testToken, "token must never appear in an error message") assert.Contains(t, err.Error(), "500") } func TestWriteFileCreatesNewFile(t *testing.T) { var getPath, postPath, postBody string srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: getPath = r.URL.Path w.WriteHeader(http.StatusNotFound) // file does not exist yet case http.MethodPost: // gitea contents API: POST = create postPath = r.URL.Path b, _ := io.ReadAll(r.Body) postBody = string(b) w.WriteHeader(http.StatusCreated) _ = json.NewEncoder(w).Encode(map[string]any{"content": map[string]any{"html_url": "https://git/x"}}) default: t.Errorf("create must POST, got %s", r.Method) } })) defer srv.Close() err := gitea.New(srv.URL, testToken).WriteFile(context.Background(), "ai-sessions", "summaries/claude-code/2026-06/2026-06-23-x-abcd1234.md", "# Summary\n\nbody\n") require.NoError(t, err) assert.Equal(t, "/api/v1/repos/mathias/ai-sessions/contents/summaries/claude-code/2026-06/2026-06-23-x-abcd1234.md", getPath) assert.Equal(t, getPath, postPath) // base64 of the content, no sha on create. assert.Contains(t, postBody, "IyBTdW1tYXJ5") // base64("# Summary") assert.NotContains(t, postBody, `"sha"`) } func TestWriteFileUpdatesExisting(t *testing.T) { var putBody string srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: w.WriteHeader(http.StatusOK) _ = json.NewEncoder(w).Encode(map[string]any{"sha": "deadbeef"}) case http.MethodPut: b, _ := io.ReadAll(r.Body) putBody = string(b) w.WriteHeader(http.StatusOK) _ = json.NewEncoder(w).Encode(map[string]any{"content": map[string]any{"html_url": "https://git/x"}}) } })) defer srv.Close() err := gitea.New(srv.URL, testToken).WriteFile(context.Background(), "ai-sessions", "p/x.md", "new") require.NoError(t, err) assert.Contains(t, putBody, `"sha":"deadbeef"`, "existing file → update with sha") } func TestWriteFileErrorNoTokenLeak(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodGet { w.WriteHeader(http.StatusNotFound) return } w.WriteHeader(http.StatusUnprocessableEntity) _, _ = w.Write([]byte("bad")) })) defer srv.Close() err := gitea.New(srv.URL, testToken).WriteFile(context.Background(), "ai-sessions", "p/x.md", "x") require.Error(t, err) assert.NotContains(t, err.Error(), testToken) assert.Contains(t, err.Error(), "422") }