174 lines
6.3 KiB
Go
174 lines
6.3 KiB
Go
package test
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestActivityLogBackendQAResetResponseIsIndistinguishable(t *testing.T) {
|
|
t.Run("unknown email answers the same bytes as an existing account", func(t *testing.T) {
|
|
e := newActivityHTTPEnv(t, 1000)
|
|
|
|
known := e.send(http.MethodPost, "/v1/auth/password-reset", `{"email":"marta@example.com"}`)
|
|
unknown := e.send(http.MethodPost, "/v1/auth/password-reset", `{"email":"nobody@example.com"}`)
|
|
|
|
if known.Code != unknown.Code || known.Body.String() != unknown.Body.String() {
|
|
t.Errorf("responses differ: %d %q versus %d %q", known.Code, known.Body.String(), unknown.Code, unknown.Body.String())
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestActivityLogBackendQAProducersSurviveRecorderFailure(t *testing.T) {
|
|
type producer func(e *activityHTTPEnv) *httptest.ResponseRecorder
|
|
producers := map[string]producer{
|
|
"login failure": func(e *activityHTTPEnv) *httptest.ResponseRecorder {
|
|
return e.send(http.MethodPost, "/v1/auth/login", `{"username":"marta","password":"wrongpassword1"}`)
|
|
},
|
|
"logout": func(e *activityHTTPEnv) *httptest.ResponseRecorder {
|
|
return e.send(http.MethodPost, "/v1/auth/logout", "", asUser(e, activityOwnerID))
|
|
},
|
|
"password reset request": func(e *activityHTTPEnv) *httptest.ResponseRecorder {
|
|
return e.send(http.MethodPost, "/v1/auth/password-reset", `{"email":"marta@example.com"}`)
|
|
},
|
|
"api key creation": func(e *activityHTTPEnv) *httptest.ResponseRecorder {
|
|
return e.send(http.MethodPost, "/v1/api-key", `{"name":"Deploy bot"}`, asUser(e, activityOwnerID))
|
|
},
|
|
"api key revoke": func(e *activityHTTPEnv) *httptest.ResponseRecorder {
|
|
_, id := e.issueKey(t, activityOwnerID, "Deploy bot")
|
|
return e.send(http.MethodDelete, "/v1/api-key/"+strconv.Itoa(int(id)), "", asUser(e, activityOwnerID))
|
|
},
|
|
"retention change": func(e *activityHTTPEnv) *httptest.ResponseRecorder {
|
|
return e.send(http.MethodPatch, "/v1/settings", `{"activity_retention_days": 30}`, asUser(e, activityOwnerID))
|
|
},
|
|
}
|
|
scrub := regexp.MustCompile(`"(id|key|prefix|created_at)":\s*("[^"]*"|[0-9]+)`)
|
|
for name, run := range producers {
|
|
t.Run(name, func(t *testing.T) {
|
|
healthy := run(newActivityHTTPEnv(t, 1000))
|
|
broken := newActivityHTTPEnv(t, 1000)
|
|
broken.probe.appendErr = errDiskFull
|
|
|
|
got := run(broken)
|
|
|
|
if got.Code != healthy.Code || scrub.ReplaceAllString(got.Body.String(), "") != scrub.ReplaceAllString(healthy.Body.String(), "") {
|
|
t.Errorf("a failing recorder changed the answer: %d %q versus %d %q", got.Code, got.Body.String(), healthy.Code, healthy.Body.String())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestActivityLogBackendQAGuards(t *testing.T) {
|
|
read := func(t *testing.T, root string, suffix string) map[string]string {
|
|
t.Helper()
|
|
files := map[string]string{}
|
|
err := filepath.Walk(filepath.Join("..", root), func(path string, info os.FileInfo, err error) error {
|
|
if err != nil || info.IsDir() || !strings.HasSuffix(path, suffix) {
|
|
return err
|
|
}
|
|
content, readErr := os.ReadFile(path)
|
|
files[path] = string(content)
|
|
return readErr
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(files) == 0 {
|
|
t.Fatalf("no files under %s", root)
|
|
}
|
|
return files
|
|
}
|
|
|
|
t.Run("The audit repository source uses no dialect specific SQL", func(t *testing.T) {
|
|
for path, source := range read(t, "internal/infrastructure/persistence/repositories", ".go") {
|
|
if !strings.Contains(filepath.Base(path), "audit") {
|
|
continue
|
|
}
|
|
for _, banned := range []string{"ILIKE", "strftime", "date_trunc"} {
|
|
if strings.Contains(source, banned) {
|
|
t.Errorf("%s contains %s", path, banned)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("The audit domain imports only the stdlib and domain packages", func(t *testing.T) {
|
|
importLine := regexp.MustCompile(`(?m)^\s*"([^"]+)"$`)
|
|
for path, source := range read(t, "internal/domain/audit", ".go") {
|
|
for _, match := range importLine.FindAllStringSubmatch(source, -1) {
|
|
if strings.Contains(match[1], ".") && !strings.HasPrefix(match[1], "RenewCMS/internal/domain/") {
|
|
t.Errorf("%s imports %s", path, match[1])
|
|
}
|
|
if strings.HasPrefix(match[1], "RenewCMS/") && !strings.HasPrefix(match[1], "RenewCMS/internal/domain/") {
|
|
t.Errorf("%s imports %s", path, match[1])
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("The activity use cases and handlers keep their layering", func(t *testing.T) {
|
|
banned := []string{"gorm.io", "infrastructure/persistence", "infrastructure/mailer", "net/http", "go-chi", "RenewCMS/api"}
|
|
for path, source := range read(t, "internal/infrastructure/useCases", ".go") {
|
|
base := filepath.Base(path)
|
|
if !strings.Contains(base, "Activity") && !strings.Contains(base, "activity") {
|
|
continue
|
|
}
|
|
for _, word := range banned {
|
|
if strings.Contains(source, word+`"`) || strings.Contains(source, `"`+word) {
|
|
t.Errorf("%s imports %s", path, word)
|
|
}
|
|
}
|
|
}
|
|
for path, source := range read(t, "api/handlers/activity", ".go") {
|
|
for _, word := range []string{"gorm.io", "persistence", "infrastructure/mailer"} {
|
|
if strings.Contains(source, word) {
|
|
t.Errorf("%s references %s", path, word)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("The persistence repository asserts the audit contract", func(t *testing.T) {
|
|
for path, source := range read(t, "internal/infrastructure/persistence/repositories", "auditEventRepository.go") {
|
|
if !strings.Contains(source, "var _ audit.Repository = &") {
|
|
t.Errorf("%s lacks the interface assertion", path)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("The new Go files hold no comment", func(t *testing.T) {
|
|
line := regexp.MustCompile(`(?m)^\s*//`)
|
|
for _, root := range []string{"internal/domain/audit", "internal/infrastructure/auditing", "api/handlers/activity"} {
|
|
for path, source := range read(t, root, ".go") {
|
|
if line.MatchString(source) {
|
|
t.Errorf("%s holds a comment", path)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("The docs describe the activity surface", func(t *testing.T) {
|
|
wants := map[string][]string{
|
|
"docs/api.md": {"/v1/activity", "/v1/activity/summary", "/v1/activity/export", "activity_retention_days"},
|
|
"docs/architecture.md": {"audit"},
|
|
"docs/configuration.md": {"retention"},
|
|
}
|
|
for doc, needles := range wants {
|
|
content, err := os.ReadFile(filepath.Join("..", doc))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, needle := range needles {
|
|
if !strings.Contains(string(content), needle) {
|
|
t.Errorf("%s does not mention %s", doc, needle)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|