gitmote / internal / webui / secrets_test.go · history · branches
2949 bytes · raw
package webui
import (
"context"
"net/http"
"net/url"
"strings"
"testing"
)
func TestSecretsGoldenPath(t *testing.T) {
x := newHarness(t)
ctx := context.Background()
session := x.login(x.mintTokenFor(x.admin.ID))
repo, err := x.md.CreateRepo(ctx, "app", "main")
if err != nil {
t.Fatalf("CreateRepo: %v", err)
}
// Set a secret. The value must never appear in the response.
const value = "hunter2-NEEDLE"
rec := x.do(http.MethodPost, "/app/secrets",
url.Values{"name": {"API_TOKEN"}, "value": {value}}, session)
if rec.Code != http.StatusOK || !strings.Contains(rec.Body.String(), "saved secret API_TOKEN") {
t.Fatalf("set secret: %d (%s)", rec.Code, rec.Body)
}
if strings.Contains(rec.Body.String(), value) {
t.Error("set-secret response echoed the value")
}
// It is stored as an envelope (name listable, value not in plaintext).
stored, _ := x.md.GetSecrets(ctx, repo.ID)
if len(stored) != 1 || stored[0].Name != "API_TOKEN" {
t.Fatalf("stored = %+v, want one API_TOKEN envelope", stored)
}
if strings.Contains(string(stored[0].CT), value) {
t.Error("stored ciphertext contains the plaintext value")
}
// The list page shows the name but never the value.
rec = x.do(http.MethodGet, "/app/secrets", nil, session)
if !strings.Contains(rec.Body.String(), "API_TOKEN") {
t.Errorf("list page missing the secret name (body: %s)", rec.Body)
}
if strings.Contains(rec.Body.String(), value) {
t.Error("list page leaked the secret value")
}
// Delete it.
rec = x.do(http.MethodPost, "/app/secrets/delete",
url.Values{"name": {"API_TOKEN"}}, session)
if rec.Code != http.StatusOK {
t.Fatalf("delete secret: %d (%s)", rec.Code, rec.Body)
}
if names, _ := x.md.ListSecretNames(ctx, repo.ID); len(names) != 0 {
t.Errorf("names after delete = %v, want empty", names)
}
}
func TestSecretsRejectsReservedName(t *testing.T) {
x := newHarness(t)
ctx := context.Background()
session := x.login(x.mintTokenFor(x.admin.ID))
if _, err := x.md.CreateRepo(ctx, "app", "main"); err != nil {
t.Fatalf("CreateRepo: %v", err)
}
rec := x.do(http.MethodPost, "/app/secrets",
url.Values{"name": {"WORKER_SECRET"}, "value": {"x"}}, session)
if rec.Code != http.StatusOK || !strings.Contains(rec.Body.String(), "reserved") {
t.Errorf("reserved name: %d (%s), want a rejection message", rec.Code, rec.Body)
}
}
func TestSecretsRequireAdmin(t *testing.T) {
x := newHarness(t)
// Unauthenticated GET redirects to login; POST is 401 — the panel is gated
// by requireAdmin, which runs before the repo is even looked up.
if rec := x.do(http.MethodGet, "/app/secrets", nil, nil); rec.Code != http.StatusSeeOther {
t.Errorf("unauth GET /app/secrets = %d, want 303 redirect", rec.Code)
}
rec := x.do(http.MethodPost, "/app/secrets",
url.Values{"name": {"K"}, "value": {"v"}}, nil)
if rec.Code != http.StatusUnauthorized {
t.Errorf("unauth POST /app/secrets = %d, want 401", rec.Code)
}
}