Update golangci-lint to v2.12.2 with canonical config
All checks were successful
Check / check (pull_request) Successful in 3m22s
All checks were successful
Check / check (pull_request) Successful in 3m22s
Bump golangci-lint from v2.10.1 to v2.12.2 in the Dockerfile lint stage (tag+digest pin) and script/bootstrap release-archive pins (linux amd64/arm64 sha256s). Replace .golangci.yml with the canonical v2-layout config so linter settings (lll 88, funlen 80/50, cyclop 15, dupl 100) actually apply. Fix all findings surfaced by the new linter and config: - noctx: use httptest.NewRequestWithContext in all tests - gosec G710/G703: route app redirects through a path-escaping redirectToApp helper; annotate internal log path usage - goconst: introduce shared constants for template/JSON keys and repeated test literals - lll: wrap lines to the 88-column limit - dupl: extract shared helpers (generic findAllByAppID in models, deleteAppResource in handlers, parsePush in webhook payloads, table-driven/helper-based test dedup) - nolintlint: drop nolint directives made obsolete by the new limits Record the change in TODO.md; make check is green.
This commit is contained in:
@@ -32,6 +32,11 @@ import (
|
||||
"sneak.berlin/go/upaas/internal/service/webhook"
|
||||
)
|
||||
|
||||
const (
|
||||
branchMain = "main"
|
||||
paramSecret = "secret"
|
||||
)
|
||||
|
||||
type testContext struct {
|
||||
handlers *handlers.Handlers
|
||||
database *database.Database
|
||||
@@ -193,7 +198,8 @@ func TestHandleHealthCheck(t *testing.T) {
|
||||
|
||||
testCtx := setupTestHandlers(t)
|
||||
|
||||
request := httptest.NewRequest(
|
||||
request := httptest.NewRequestWithContext(
|
||||
t.Context(),
|
||||
http.MethodGet,
|
||||
"/.well-known/healthcheck.json",
|
||||
nil,
|
||||
@@ -210,6 +216,26 @@ func TestHandleHealthCheck(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// assertPageRenders serves a GET request for path with the given
|
||||
// handler and asserts a 200 response containing want.
|
||||
func assertPageRenders(
|
||||
t *testing.T,
|
||||
handler http.Handler,
|
||||
path, want string,
|
||||
) {
|
||||
t.Helper()
|
||||
|
||||
request := httptest.NewRequestWithContext(
|
||||
t.Context(), http.MethodGet, path, nil,
|
||||
)
|
||||
recorder := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(recorder, request)
|
||||
|
||||
assert.Equal(t, http.StatusOK, recorder.Code)
|
||||
assert.Contains(t, recorder.Body.String(), want)
|
||||
}
|
||||
|
||||
func TestHandleSetupGET(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -217,15 +243,7 @@ func TestHandleSetupGET(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCtx := setupTestHandlers(t)
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "/setup", nil)
|
||||
recorder := httptest.NewRecorder()
|
||||
|
||||
handler := testCtx.handlers.HandleSetupGET()
|
||||
handler.ServeHTTP(recorder, request)
|
||||
|
||||
assert.Equal(t, http.StatusOK, recorder.Code)
|
||||
assert.Contains(t, recorder.Body.String(), "setup")
|
||||
assertPageRenders(t, testCtx.handlers.HandleSetupGET(), "/setup", "setup")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -237,7 +255,8 @@ func createSetupFormRequest(
|
||||
form.Set("password", password)
|
||||
form.Set("password_confirm", confirm)
|
||||
|
||||
request := httptest.NewRequest(
|
||||
request := httptest.NewRequestWithContext(
|
||||
context.Background(),
|
||||
http.MethodPost,
|
||||
"/setup",
|
||||
strings.NewReader(form.Encode()),
|
||||
@@ -314,15 +333,7 @@ func TestHandleLoginGET(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCtx := setupTestHandlers(t)
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "/login", nil)
|
||||
recorder := httptest.NewRecorder()
|
||||
|
||||
handler := testCtx.handlers.HandleLoginGET()
|
||||
handler.ServeHTTP(recorder, request)
|
||||
|
||||
assert.Equal(t, http.StatusOK, recorder.Code)
|
||||
assert.Contains(t, recorder.Body.String(), "login")
|
||||
assertPageRenders(t, testCtx.handlers.HandleLoginGET(), "/login", "login")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -331,7 +342,8 @@ func createLoginFormRequest(username, password string) *http.Request {
|
||||
form.Set("username", username)
|
||||
form.Set("password", password)
|
||||
|
||||
request := httptest.NewRequest(
|
||||
request := httptest.NewRequestWithContext(
|
||||
context.Background(),
|
||||
http.MethodPost,
|
||||
"/login",
|
||||
strings.NewReader(form.Encode()),
|
||||
@@ -395,7 +407,9 @@ func TestHandleDashboard(t *testing.T) {
|
||||
|
||||
testCtx := setupTestHandlers(t)
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
request := httptest.NewRequestWithContext(
|
||||
t.Context(), http.MethodGet, "/", nil,
|
||||
)
|
||||
recorder := httptest.NewRecorder()
|
||||
|
||||
handler := testCtx.handlers.HandleDashboard()
|
||||
@@ -413,7 +427,9 @@ func TestHandleDashboard(t *testing.T) {
|
||||
// Create an app so the template iterates over AppStats and hits .CSRFField
|
||||
createTestApp(t, testCtx, "csrf-test-app")
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
request := httptest.NewRequestWithContext(
|
||||
t.Context(), http.MethodGet, "/", nil,
|
||||
)
|
||||
recorder := httptest.NewRecorder()
|
||||
|
||||
handler := testCtx.handlers.HandleDashboard()
|
||||
@@ -433,7 +449,9 @@ func TestHandleAppNew(t *testing.T) {
|
||||
|
||||
testCtx := setupTestHandlers(t)
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "/apps/new", nil)
|
||||
request := httptest.NewRequestWithContext(
|
||||
t.Context(), http.MethodGet, "/apps/new", nil,
|
||||
)
|
||||
recorder := httptest.NewRecorder()
|
||||
|
||||
handler := testCtx.handlers.HandleAppNew()
|
||||
@@ -472,7 +490,7 @@ func createTestApp(
|
||||
app.CreateAppInput{
|
||||
Name: name,
|
||||
RepoURL: "git@example.com:user/" + name + ".git",
|
||||
Branch: "main",
|
||||
Branch: branchMain,
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
@@ -493,7 +511,7 @@ func TestHandleWebhookRejectsOversizedBody(t *testing.T) {
|
||||
app.CreateAppInput{
|
||||
Name: "oversize-test-app",
|
||||
RepoURL: "git@example.com:user/repo.git",
|
||||
Branch: "main",
|
||||
Branch: branchMain,
|
||||
},
|
||||
)
|
||||
require.NoError(t, createErr)
|
||||
@@ -501,14 +519,15 @@ func TestHandleWebhookRejectsOversizedBody(t *testing.T) {
|
||||
// Create a body larger than 1MB - it should be silently truncated
|
||||
// and the webhook should still process (or fail gracefully on parse)
|
||||
largePayload := strings.Repeat("x", 2*1024*1024) // 2MB
|
||||
request := httptest.NewRequest(
|
||||
request := httptest.NewRequestWithContext(
|
||||
t.Context(),
|
||||
http.MethodPost,
|
||||
"/webhook/"+createdApp.WebhookSecret,
|
||||
strings.NewReader(largePayload),
|
||||
)
|
||||
request = addChiURLParams(
|
||||
request,
|
||||
map[string]string{"secret": createdApp.WebhookSecret},
|
||||
map[string]string{paramSecret: createdApp.WebhookSecret},
|
||||
)
|
||||
request.Header.Set("Content-Type", "application/json")
|
||||
request.Header.Set("X-Gitea-Event", "push")
|
||||
@@ -544,7 +563,8 @@ func testOwnershipVerification(t *testing.T, cfg ownedResourceTestConfig) {
|
||||
|
||||
resourceID := cfg.createFn(t, testCtx, app1)
|
||||
|
||||
request := httptest.NewRequest(
|
||||
request := httptest.NewRequestWithContext(
|
||||
t.Context(),
|
||||
http.MethodPost,
|
||||
cfg.deletePath(app2.ID, resourceID),
|
||||
nil,
|
||||
@@ -583,7 +603,8 @@ func TestHandleEnvVarSaveBulk(t *testing.T) {
|
||||
r := chi.NewRouter()
|
||||
r.Post("/apps/{id}/env", testCtx.handlers.HandleEnvVarSave())
|
||||
|
||||
request := httptest.NewRequest(
|
||||
request := httptest.NewRequestWithContext(
|
||||
t.Context(),
|
||||
http.MethodPost,
|
||||
"/apps/"+createdApp.ID+"/env",
|
||||
strings.NewReader(body),
|
||||
@@ -625,7 +646,8 @@ func TestHandleEnvVarSaveAppNotFound(t *testing.T) {
|
||||
r := chi.NewRouter()
|
||||
r.Post("/apps/{id}/env", testCtx.handlers.HandleEnvVarSave())
|
||||
|
||||
request := httptest.NewRequest(
|
||||
request := httptest.NewRequestWithContext(
|
||||
t.Context(),
|
||||
http.MethodPost,
|
||||
"/apps/nonexistent-id/env",
|
||||
strings.NewReader(body),
|
||||
@@ -651,7 +673,8 @@ func TestHandleEnvVarSaveEmptyKeyRejected(t *testing.T) {
|
||||
r := chi.NewRouter()
|
||||
r.Post("/apps/{id}/env", testCtx.handlers.HandleEnvVarSave())
|
||||
|
||||
request := httptest.NewRequest(
|
||||
request := httptest.NewRequestWithContext(
|
||||
t.Context(),
|
||||
http.MethodPost,
|
||||
"/apps/"+createdApp.ID+"/env",
|
||||
strings.NewReader(body),
|
||||
@@ -673,12 +696,14 @@ func TestHandleEnvVarSaveDuplicateKeyRejected(t *testing.T) {
|
||||
createdApp := createTestApp(t, testCtx, "envvar-dedup-app")
|
||||
|
||||
// Send two entries with the same key — should be rejected
|
||||
body := `[{"key":"FOO","value":"first"},{"key":"BAR","value":"bar"},{"key":"FOO","value":"second"}]`
|
||||
body := `[{"key":"FOO","value":"first"},{"key":"BAR","value":"bar"},` +
|
||||
`{"key":"FOO","value":"second"}]`
|
||||
|
||||
r := chi.NewRouter()
|
||||
r.Post("/apps/{id}/env", testCtx.handlers.HandleEnvVarSave())
|
||||
|
||||
request := httptest.NewRequest(
|
||||
request := httptest.NewRequestWithContext(
|
||||
t.Context(),
|
||||
http.MethodPost,
|
||||
"/apps/"+createdApp.ID+"/env",
|
||||
strings.NewReader(body),
|
||||
@@ -716,7 +741,8 @@ func TestHandleEnvVarSaveCrossAppIsolation(t *testing.T) {
|
||||
r := chi.NewRouter()
|
||||
r.Post("/apps/{id}/env", testCtx.handlers.HandleEnvVarSave())
|
||||
|
||||
request := httptest.NewRequest(
|
||||
request := httptest.NewRequestWithContext(
|
||||
t.Context(),
|
||||
http.MethodPost,
|
||||
"/apps/"+appA.ID+"/env",
|
||||
strings.NewReader(body),
|
||||
@@ -779,7 +805,8 @@ func TestHandleEnvVarSaveBodySizeLimit(t *testing.T) {
|
||||
r := chi.NewRouter()
|
||||
r.Post("/apps/{id}/env", testCtx.handlers.HandleEnvVarSave())
|
||||
|
||||
request := httptest.NewRequest(
|
||||
request := httptest.NewRequestWithContext(
|
||||
t.Context(),
|
||||
http.MethodPost,
|
||||
"/apps/"+createdApp.ID+"/env",
|
||||
strings.NewReader(sb.String()),
|
||||
@@ -848,7 +875,8 @@ func TestDeleteVolumeOwnershipVerification(t *testing.T) {
|
||||
require.NoError(t, volume.Save(context.Background()))
|
||||
|
||||
// Try to delete app1's volume using app2's URL path
|
||||
request := httptest.NewRequest(
|
||||
request := httptest.NewRequestWithContext(
|
||||
t.Context(),
|
||||
http.MethodPost,
|
||||
"/apps/"+app2.ID+"/volumes/"+strconv.FormatInt(volume.ID, 10)+"/delete",
|
||||
nil,
|
||||
@@ -889,7 +917,8 @@ func TestDeletePortOwnershipVerification(t *testing.T) {
|
||||
require.NoError(t, port.Save(context.Background()))
|
||||
|
||||
// Try to delete app1's port using app2's URL path
|
||||
request := httptest.NewRequest(
|
||||
request := httptest.NewRequestWithContext(
|
||||
t.Context(),
|
||||
http.MethodPost,
|
||||
"/apps/"+app2.ID+"/ports/"+strconv.FormatInt(port.ID, 10)+"/delete",
|
||||
nil,
|
||||
@@ -930,7 +959,8 @@ func TestHandleEnvVarSaveEmptyClears(t *testing.T) {
|
||||
r := chi.NewRouter()
|
||||
r.Post("/apps/{id}/env", testCtx.handlers.HandleEnvVarSave())
|
||||
|
||||
request := httptest.NewRequest(
|
||||
request := httptest.NewRequestWithContext(
|
||||
t.Context(),
|
||||
http.MethodPost,
|
||||
"/apps/"+createdApp.ID+"/env",
|
||||
strings.NewReader("[]"),
|
||||
@@ -979,7 +1009,8 @@ func TestHandleVolumeAddValidatesPaths(t *testing.T) {
|
||||
form.Set("host_path", tt.hostPath)
|
||||
form.Set("container_path", tt.containerPath)
|
||||
|
||||
request := httptest.NewRequest(
|
||||
request := httptest.NewRequestWithContext(
|
||||
t.Context(),
|
||||
http.MethodPost,
|
||||
"/apps/"+createdApp.ID+"/volumes",
|
||||
strings.NewReader(form.Encode()),
|
||||
@@ -1016,7 +1047,8 @@ func TestHandleVolumeAddValidatesPaths(t *testing.T) {
|
||||
}
|
||||
|
||||
// TestSetupRequiredExemptsHealthAndStaticAndAPI verifies that the SetupRequired
|
||||
// middleware allows /health, /s/*, and /api/* paths through even when setup is required.
|
||||
// middleware allows /health, /s/*, and /api/* paths through even when setup is
|
||||
// required.
|
||||
func TestSetupRequiredExemptsHealthAndStaticAndAPI(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -1032,13 +1064,21 @@ func TestSetupRequiredExemptsHealthAndStaticAndAPI(t *testing.T) {
|
||||
|
||||
wrapped := mw(okHandler)
|
||||
|
||||
exemptPaths := []string{"/health", "/s/style.css", "/s/js/app.js", "/api/v1/apps", "/api/v1/login"}
|
||||
exemptPaths := []string{
|
||||
"/health",
|
||||
"/s/style.css",
|
||||
"/s/js/app.js",
|
||||
"/api/v1/apps",
|
||||
"/api/v1/login",
|
||||
}
|
||||
|
||||
for _, path := range exemptPaths {
|
||||
t.Run(path, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, path, nil)
|
||||
req := httptest.NewRequestWithContext(
|
||||
t.Context(), http.MethodGet, path, nil,
|
||||
)
|
||||
rr := httptest.NewRecorder()
|
||||
wrapped.ServeHTTP(rr, req)
|
||||
|
||||
@@ -1051,7 +1091,9 @@ func TestSetupRequiredExemptsHealthAndStaticAndAPI(t *testing.T) {
|
||||
t.Run("non-exempt redirects", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req := httptest.NewRequestWithContext(
|
||||
t.Context(), http.MethodGet, "/", nil,
|
||||
)
|
||||
rr := httptest.NewRecorder()
|
||||
wrapped.ServeHTTP(rr, req)
|
||||
|
||||
@@ -1067,7 +1109,8 @@ func TestHandleCancelDeployRedirects(t *testing.T) {
|
||||
|
||||
createdApp := createTestApp(t, testCtx, "cancel-deploy-app")
|
||||
|
||||
request := httptest.NewRequest(
|
||||
request := httptest.NewRequestWithContext(
|
||||
t.Context(),
|
||||
http.MethodPost,
|
||||
"/apps/"+createdApp.ID+"/deployments/cancel",
|
||||
nil,
|
||||
@@ -1087,7 +1130,8 @@ func TestHandleCancelDeployReturns404ForUnknownApp(t *testing.T) {
|
||||
|
||||
testCtx := setupTestHandlers(t)
|
||||
|
||||
request := httptest.NewRequest(
|
||||
request := httptest.NewRequestWithContext(
|
||||
t.Context(),
|
||||
http.MethodPost,
|
||||
"/apps/nonexistent/deployments/cancel",
|
||||
nil,
|
||||
@@ -1108,12 +1152,16 @@ func TestHandleWebhookReturns404ForUnknownSecret(t *testing.T) {
|
||||
|
||||
webhookURL := "/webhook/unknown-secret"
|
||||
payload := `{"ref": "refs/heads/main"}`
|
||||
request := httptest.NewRequest(
|
||||
request := httptest.NewRequestWithContext(
|
||||
t.Context(),
|
||||
http.MethodPost,
|
||||
webhookURL,
|
||||
strings.NewReader(payload),
|
||||
)
|
||||
request = addChiURLParams(request, map[string]string{"secret": "unknown-secret"})
|
||||
request = addChiURLParams(
|
||||
request,
|
||||
map[string]string{paramSecret: "unknown-secret"},
|
||||
)
|
||||
request.Header.Set("Content-Type", "application/json")
|
||||
request.Header.Set("X-Gitea-Event", "push")
|
||||
|
||||
@@ -1136,21 +1184,22 @@ func TestHandleWebhookProcessesValidWebhook(t *testing.T) {
|
||||
app.CreateAppInput{
|
||||
Name: "webhook-test-app",
|
||||
RepoURL: "git@example.com:user/repo.git",
|
||||
Branch: "main",
|
||||
Branch: branchMain,
|
||||
},
|
||||
)
|
||||
require.NoError(t, createErr)
|
||||
|
||||
payload := `{"ref": "refs/heads/main", "after": "abc123"}`
|
||||
webhookURL := "/webhook/" + createdApp.WebhookSecret
|
||||
request := httptest.NewRequest(
|
||||
request := httptest.NewRequestWithContext(
|
||||
t.Context(),
|
||||
http.MethodPost,
|
||||
webhookURL,
|
||||
strings.NewReader(payload),
|
||||
)
|
||||
request = addChiURLParams(
|
||||
request,
|
||||
map[string]string{"secret": createdApp.WebhookSecret},
|
||||
map[string]string{paramSecret: createdApp.WebhookSecret},
|
||||
)
|
||||
request.Header.Set("Content-Type", "application/json")
|
||||
request.Header.Set("X-Gitea-Event", "push")
|
||||
|
||||
Reference in New Issue
Block a user