package gitea_test import ( "context" "encoding/base64" "encoding/json" "errors" "io" "net/http" "net/http/httptest" "sync/atomic" "testing" "gitea.d-ma.be/mathias/gitea-mcp/internal/gitea" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestGenerateFromTemplate(t *testing.T) { var capturedBody []byte srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, "/api/v1/repos/mathias/template-go-web/generate", r.URL.Path) assert.Equal(t, http.MethodPost, r.Method) var err error capturedBody, err = io.ReadAll(r.Body) require.NoError(t, err) w.WriteHeader(http.StatusCreated) w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{ "name":"new-svc", "full_name":"mathias/new-svc", "default_branch":"main", "description":"A new service", "private":true, "clone_url":"http://gitea.example.com/mathias/new-svc.git", "html_url":"http://gitea.example.com/mathias/new-svc", "template":false }`)) })) defer srv.Close() c := gitea.NewClient(srv.URL, "tok") repo, err := c.GenerateFromTemplate(context.Background(), "mathias", "template-go-web", gitea.GenerateFromTemplateArgs{ Owner: "mathias", Name: "new-svc", Description: "A new service", Private: true, GitContent: true, }) require.NoError(t, err) // Verify the captured POST body contains the expected fields. var payload map[string]any require.NoError(t, json.Unmarshal(capturedBody, &payload)) assert.Equal(t, "mathias", payload["owner"]) assert.Equal(t, "new-svc", payload["name"]) assert.Equal(t, "A new service", payload["description"]) assert.Equal(t, true, payload["private"]) assert.Equal(t, true, payload["git_content"]) // Verify the decoded repo fields. assert.Equal(t, "new-svc", repo.Name) assert.Equal(t, "mathias/new-svc", repo.FullName) assert.Equal(t, "main", repo.DefaultBranch) assert.Equal(t, "A new service", repo.Description) assert.True(t, repo.Private) assert.Equal(t, "http://gitea.example.com/mathias/new-svc.git", repo.CloneURL) assert.Equal(t, "http://gitea.example.com/mathias/new-svc", repo.HTMLURL) } func TestSubstituteFileApplies(t *testing.T) { originalContent := "module __MODULE_PATH__\n\ngo 1.22\n" encoded := base64.StdEncoding.EncodeToString([]byte(originalContent)) var capturedPutBody []byte srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: assert.Equal(t, "/api/v1/repos/mathias/new-svc/contents/go.mod", r.URL.Path) assert.Equal(t, "main", r.URL.Query().Get("ref")) w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{"path":"go.mod","sha":"abc123","size":30,"content":"` + encoded + `","encoding":"base64"}`)) case http.MethodPut: assert.Equal(t, "/api/v1/repos/mathias/new-svc/contents/go.mod", r.URL.Path) var err error capturedPutBody, err = io.ReadAll(r.Body) require.NoError(t, err) w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte(`{"content":{"path":"go.mod","sha":"newsha","html_url":""},"commit":{"sha":"commitsha","html_url":""}}`)) default: t.Errorf("unexpected method %s", r.Method) w.WriteHeader(http.StatusMethodNotAllowed) } })) defer srv.Close() c := gitea.NewClient(srv.URL, "tok") err := c.SubstituteFile(context.Background(), "mathias", "new-svc", "main", "go.mod", map[string]string{ "__MODULE_PATH__": "gitea.d-ma.be/mathias/new-svc", }) require.NoError(t, err) // Verify the PUT body contains the substituted content. require.NotNil(t, capturedPutBody, "PUT should have been called") var payload map[string]string require.NoError(t, json.Unmarshal(capturedPutBody, &payload)) decoded, err := base64.StdEncoding.DecodeString(payload["content"]) require.NoError(t, err) assert.Contains(t, string(decoded), "gitea.d-ma.be/mathias/new-svc") assert.NotContains(t, string(decoded), "__MODULE_PATH__") assert.Equal(t, "abc123", payload["sha"]) assert.Equal(t, "Apply template substitutions", payload["message"]) } func TestSubstituteFileNoChangeSkipsWrite(t *testing.T) { originalContent := "module gitea.d-ma.be/mathias/existing\n\ngo 1.22\n" encoded := base64.StdEncoding.EncodeToString([]byte(originalContent)) var putCount atomic.Int32 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{"path":"go.mod","sha":"abc123","size":40,"content":"` + encoded + `","encoding":"base64"}`)) case http.MethodPut: putCount.Add(1) w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte(`{"content":{"path":"go.mod","sha":"newsha","html_url":""},"commit":{"sha":"c","html_url":""}}`)) } })) defer srv.Close() c := gitea.NewClient(srv.URL, "tok") // Replacements that don't match anything in the content. err := c.SubstituteFile(context.Background(), "mathias", "new-svc", "main", "go.mod", map[string]string{ "__MODULE_PATH__": "gitea.d-ma.be/mathias/new-svc", }) require.NoError(t, err) assert.Equal(t, int32(0), putCount.Load(), "PUT should not be called when content is unchanged") } func TestSubstituteFileReadError(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotFound) _, _ = w.Write([]byte(`{"message":"file not found"}`)) })) defer srv.Close() c := gitea.NewClient(srv.URL, "tok") err := c.SubstituteFile(context.Background(), "mathias", "new-svc", "main", "go.mod", map[string]string{ "__MODULE_PATH__": "gitea.d-ma.be/mathias/new-svc", }) require.Error(t, err) assert.True(t, errors.Is(err, gitea.ErrNotFound), "error should wrap ErrNotFound, got: %v", err) }