go-scm/pkg/api/provider_test.go
Snider f3dd8ca0f0 fix(review): address CodeRabbit PR #2 findings
Critical/Major:
- Remove dead functions syncRepoNameFromArg and repoNameFromArg (used url pkg without import, would cause compile error)
- Migrate forge.lthn.ai/core/config → dappco.re/go/core/config in forge/config.go and gitea/config.go
- Propagate ListIssueCommentsIter errors in forge/meta.go and gitea/meta.go (was silently returning truncated count)
- Add RedactedToken() to gitea/client.go to avoid exposing raw API tokens
- Add 30s timeout to http.DefaultClient usage in gitea/prs.go via package-level httpClient
- Fix stringsx.Fields (bufio 64KiB limit), Repeat (wrong for negative/zero), Replace (ignored n param) to match stdlib
- Fix fmtx.Println to use fmt.Sprintln so spaces appear between operands
- Fix filepathx.Abs to use path/filepath for OS-aware path handling; wrap Getwd error
- Fix stdio.Write to return io.ErrShortWrite on partial writes
- Add mutex lock to jobrunner.Journal.Query to prevent data race with Append
- Add sync.RWMutex to ScmProvider; protect p.index reads/writes in pkg/api/provider.go
- Fix cmd/scm/cmd_index.go: append dir to repoPaths only after ReadDir confirms existence
- Fix manifest/compile.go: copy manifest before applying version override to avoid mutating caller
- Fix forge/labels.go: use ListOrgLabelsIter/ListRepoLabelsIter names in iterator error logs
- Wrap single-segment validation error in syncutil.ParseRepoName with function context

Minor:
- Fix import ordering (stdlib → forge.lthn.ai → third-party) in cmd/forge, cmd/collect, repos, cmd/gitea files
- Add t.Setenv("HOME", t.TempDir()) to gitea testhelpers and forge/labels_test.go
- Add iterator yield guard in forge/orgs_test.go
- Convert syncutil/repo_name_test.go to table-driven tests
- Use json.Marshal in pkg/api/provider_test.go instead of string concatenation
- Fix test naming (redundant/conflicting _Good/_Bad suffixes) across 10 test files

Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-07 09:25:42 +01:00

329 lines
9 KiB
Go

// SPDX-License-Identifier: EUPL-1.2
package api_test
import (
"bytes"
filepath "dappco.re/go/core/scm/internal/ax/filepathx"
json "dappco.re/go/core/scm/internal/ax/jsonx"
"net/http"
"net/http/httptest"
"testing"
goapi "dappco.re/go/core/api"
"dappco.re/go/core/io"
"dappco.re/go/core/scm/marketplace"
scmapi "dappco.re/go/core/scm/pkg/api"
"dappco.re/go/core/scm/repos"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func init() {
gin.SetMode(gin.TestMode)
}
// -- Provider Identity --------------------------------------------------------
func TestScmProvider_Name_Good(t *testing.T) {
p := scmapi.NewProvider(nil, nil, nil, nil)
assert.Equal(t, "scm", p.Name())
}
func TestScmProvider_BasePath_Good(t *testing.T) {
p := scmapi.NewProvider(nil, nil, nil, nil)
assert.Equal(t, "/api/v1/scm", p.BasePath())
}
func TestScmProvider_Channels_Good(t *testing.T) {
p := scmapi.NewProvider(nil, nil, nil, nil)
channels := p.Channels()
assert.Contains(t, channels, "scm.marketplace.refreshed")
assert.Contains(t, channels, "scm.marketplace.installed")
assert.Contains(t, channels, "scm.marketplace.removed")
assert.Contains(t, channels, "scm.installed.changed")
assert.Contains(t, channels, "scm.manifest.verified")
assert.Contains(t, channels, "scm.registry.changed")
}
func TestScmProvider_Element_Good(t *testing.T) {
p := scmapi.NewProvider(nil, nil, nil, nil)
el := p.Element()
assert.Equal(t, "core-scm-panel", el.Tag)
assert.Equal(t, "/assets/core-scm.js", el.Source)
}
func TestScmProvider_Describe_Good(t *testing.T) {
p := scmapi.NewProvider(nil, nil, nil, nil)
descs := p.Describe()
assert.GreaterOrEqual(t, len(descs), 11)
for _, d := range descs {
assert.NotEmpty(t, d.Method)
assert.NotEmpty(t, d.Path)
assert.NotEmpty(t, d.Summary)
assert.NotEmpty(t, d.Tags)
}
}
// -- Marketplace Endpoints ----------------------------------------------------
func TestScmProvider_ListMarketplace_Good(t *testing.T) {
idx := &marketplace.Index{
Version: 1,
Modules: []marketplace.Module{
{Code: "analytics", Name: "Analytics", Category: "product"},
{Code: "bio", Name: "Bio Links", Category: "product"},
},
Categories: []string{"product"},
}
p := scmapi.NewProvider(idx, nil, nil, nil)
r := setupRouter(p)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/scm/marketplace", nil)
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var resp goapi.Response[[]marketplace.Module]
err := json.Unmarshal(w.Body.Bytes(), &resp)
require.NoError(t, err)
assert.True(t, resp.Success)
assert.Len(t, resp.Data, 2)
}
func TestScmProvider_ListMarketplace_Search_Good(t *testing.T) {
idx := &marketplace.Index{
Version: 1,
Modules: []marketplace.Module{
{Code: "analytics", Name: "Analytics", Category: "product"},
{Code: "bio", Name: "Bio Links", Category: "product"},
},
}
p := scmapi.NewProvider(idx, nil, nil, nil)
r := setupRouter(p)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/scm/marketplace?q=bio", nil)
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var resp goapi.Response[[]marketplace.Module]
err := json.Unmarshal(w.Body.Bytes(), &resp)
require.NoError(t, err)
assert.Len(t, resp.Data, 1)
assert.Equal(t, "bio", resp.Data[0].Code)
}
func TestScmProvider_ListMarketplace_NilIndex_Good(t *testing.T) {
p := scmapi.NewProvider(nil, nil, nil, nil)
r := setupRouter(p)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/scm/marketplace", nil)
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var resp goapi.Response[[]marketplace.Module]
err := json.Unmarshal(w.Body.Bytes(), &resp)
require.NoError(t, err)
assert.True(t, resp.Success)
assert.Empty(t, resp.Data)
}
func TestScmProvider_GetMarketplaceItem_Good(t *testing.T) {
idx := &marketplace.Index{
Version: 1,
Modules: []marketplace.Module{
{Code: "analytics", Name: "Analytics", Category: "product"},
},
}
p := scmapi.NewProvider(idx, nil, nil, nil)
r := setupRouter(p)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/scm/marketplace/analytics", nil)
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var resp goapi.Response[marketplace.Module]
err := json.Unmarshal(w.Body.Bytes(), &resp)
require.NoError(t, err)
assert.Equal(t, "analytics", resp.Data.Code)
}
func TestScmProvider_GetMarketplaceItem_Bad(t *testing.T) {
idx := &marketplace.Index{Version: 1}
p := scmapi.NewProvider(idx, nil, nil, nil)
r := setupRouter(p)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/scm/marketplace/nonexistent", nil)
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusNotFound, w.Code)
}
func TestScmProvider_GetMarketplaceItem_Bad_PathTraversal_Good(t *testing.T) {
idx := &marketplace.Index{Version: 1}
p := scmapi.NewProvider(idx, nil, nil, nil)
r := setupRouter(p)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/scm/marketplace/%2e%2e", nil)
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
}
// -- Installed Endpoints ------------------------------------------------------
func TestScmProvider_ListInstalled_NilInstaller_Good(t *testing.T) {
p := scmapi.NewProvider(nil, nil, nil, nil)
r := setupRouter(p)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/scm/installed", nil)
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var resp goapi.Response[[]marketplace.InstalledModule]
err := json.Unmarshal(w.Body.Bytes(), &resp)
require.NoError(t, err)
assert.True(t, resp.Success)
assert.Empty(t, resp.Data)
}
// -- Registry Endpoints -------------------------------------------------------
func TestScmProvider_ListRegistry_NilRegistry_Good(t *testing.T) {
p := scmapi.NewProvider(nil, nil, nil, nil)
r := setupRouter(p)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/scm/registry", nil)
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var resp goapi.Response[[]any]
err := json.Unmarshal(w.Body.Bytes(), &resp)
require.NoError(t, err)
assert.True(t, resp.Success)
assert.Empty(t, resp.Data)
}
func TestScmProvider_ListRegistry_TopologicalOrder_Good(t *testing.T) {
medium := io.NewMockMedium()
require.NoError(t, medium.Write("/tmp/repos.yaml", `
version: 1
org: host-uk
base_path: /tmp/repos
repos:
core-php:
type: foundation
core-admin:
type: module
depends_on: [core-php]
`))
reg, err := repos.LoadRegistry(medium, "/tmp/repos.yaml")
require.NoError(t, err)
p := scmapi.NewProvider(nil, nil, reg, nil)
r := setupRouter(p)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/scm/registry", nil)
r.ServeHTTP(w, req)
require.Equal(t, http.StatusOK, w.Code)
var resp goapi.Response[[]map[string]any]
err = json.Unmarshal(w.Body.Bytes(), &resp)
require.NoError(t, err)
require.Len(t, resp.Data, 2)
assert.Equal(t, "core-php", resp.Data[0]["name"])
assert.Equal(t, "core-admin", resp.Data[1]["name"])
}
func TestScmProvider_RefreshMarketplace_Good(t *testing.T) {
dir := t.TempDir()
indexPath := filepath.Join(dir, "index.json")
idx := &marketplace.Index{
Version: 1,
Modules: []marketplace.Module{
{Code: "refreshed", Name: "Refreshed Module"},
},
}
require.NoError(t, marketplace.WriteIndex(io.Local, indexPath, idx))
p := scmapi.NewProvider(nil, nil, nil, nil)
r := setupRouter(p)
w := httptest.NewRecorder()
body, err := json.Marshal(map[string]string{"index_path": indexPath})
require.NoError(t, err)
req, _ := http.NewRequest("POST", "/api/v1/scm/marketplace/refresh", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var resp goapi.Response[map[string]any]
err = json.Unmarshal(w.Body.Bytes(), &resp)
require.NoError(t, err)
assert.True(t, resp.Success)
assert.Equal(t, float64(1), resp.Data["modules"])
w = httptest.NewRecorder()
req, _ = http.NewRequest("GET", "/api/v1/scm/marketplace", nil)
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var listed goapi.Response[[]marketplace.Module]
err = json.Unmarshal(w.Body.Bytes(), &listed)
require.NoError(t, err)
require.Len(t, listed.Data, 1)
assert.Equal(t, "refreshed", listed.Data[0].Code)
}
// -- Route Registration -------------------------------------------------------
func TestScmProvider_RegistersAsRouteGroup_Good(t *testing.T) {
p := scmapi.NewProvider(nil, nil, nil, nil)
engine, err := goapi.New()
require.NoError(t, err)
engine.Register(p)
assert.Len(t, engine.Groups(), 1)
assert.Equal(t, "scm", engine.Groups()[0].Name())
}
func TestScmProvider_Channels_RegisterAsStreamGroup_Good(t *testing.T) {
p := scmapi.NewProvider(nil, nil, nil, nil)
engine, err := goapi.New()
require.NoError(t, err)
engine.Register(p)
channels := engine.Channels()
assert.Contains(t, channels, "scm.marketplace.refreshed")
}
// -- Test helpers -------------------------------------------------------------
func setupRouter(p *scmapi.ScmProvider) *gin.Engine {
r := gin.New()
rg := r.Group(p.BasePath())
p.RegisterRoutes(rg)
return r
}