dev-pod-api-build/internal/k8s/forgejo_test.go
2026-04-16 04:16:36 +00:00

369 lines
11 KiB
Go

package k8s
import (
"os"
"path/filepath"
"runtime"
"strings"
"testing"
"k8s.io/apimachinery/pkg/util/yaml"
)
func toInt64(v interface{}) int64 {
switch n := v.(type) {
case int64:
return n
case float64:
return int64(n)
case int:
return int64(n)
default:
return 0
}
}
func forgejoYAMLPath() string {
_, thisFile, _, _ := runtime.Caller(0)
return filepath.Join(filepath.Dir(thisFile), "..", "..", "..", "..", "k8s", "dev-infra", "forgejo.yaml")
}
func readForgejoDocuments(t *testing.T) []map[string]interface{} {
t.Helper()
data, err := os.ReadFile(forgejoYAMLPath())
if err != nil {
t.Fatalf("reading forgejo.yaml: %v", err)
}
var docs []map[string]interface{}
parts := strings.Split(string(data), "\n---")
for _, part := range parts {
trimmed := strings.TrimSpace(part)
if trimmed == "" {
continue
}
// Strip leading comment lines (file-level comments before first doc)
lines := strings.Split(trimmed, "\n")
var contentLines []string
for _, line := range lines {
stripped := strings.TrimSpace(line)
if len(contentLines) == 0 && (stripped == "" || strings.HasPrefix(stripped, "#")) {
continue
}
contentLines = append(contentLines, line)
}
content := strings.Join(contentLines, "\n")
if content == "" {
continue
}
var doc map[string]interface{}
if err := yaml.NewYAMLOrJSONDecoder(strings.NewReader(content), 4096).Decode(&doc); err != nil {
t.Fatalf("decoding YAML document: %v\n---\n%s", err, content[:min(200, len(content))])
}
docs = append(docs, doc)
}
return docs
}
func findDoc(docs []map[string]interface{}, kind, name string) map[string]interface{} {
for _, doc := range docs {
if doc["kind"] == kind {
meta, ok := doc["metadata"].(map[string]interface{})
if ok && meta["name"] == name {
return doc
}
}
}
return nil
}
func TestForgejoYAMLExists(t *testing.T) {
path := forgejoYAMLPath()
if _, err := os.Stat(path); os.IsNotExist(err) {
t.Fatalf("forgejo.yaml does not exist at %s", path)
}
}
func TestForgejoYAMLDocumentCount(t *testing.T) {
docs := readForgejoDocuments(t)
// Expected: Secret (admin), ConfigMap, PVC, Deployment, Service, IngressRoute, Middleware (basic-auth), Secret (htpasswd), Middleware (trailing-slash)
if len(docs) < 9 {
kinds := make([]string, len(docs))
for i, d := range docs {
kinds[i] = d["kind"].(string)
}
t.Errorf("expected at least 9 YAML documents, got %d: %v", len(docs), kinds)
}
}
func TestForgejoAdminSecret(t *testing.T) {
docs := readForgejoDocuments(t)
doc := findDoc(docs, "Secret", "forgejo-admin-secret")
if doc == nil {
t.Fatal("missing forgejo-admin-secret Secret")
}
meta := doc["metadata"].(map[string]interface{})
if meta["namespace"] != "dev-infra" {
t.Errorf("expected namespace dev-infra, got %v", meta["namespace"])
}
}
func TestForgejoConfigMap(t *testing.T) {
docs := readForgejoDocuments(t)
doc := findDoc(docs, "ConfigMap", "forgejo-config")
if doc == nil {
t.Fatal("missing forgejo-config ConfigMap")
}
meta := doc["metadata"].(map[string]interface{})
if meta["namespace"] != "dev-infra" {
t.Errorf("expected namespace dev-infra, got %v", meta["namespace"])
}
data := doc["data"].(map[string]interface{})
appIni, ok := data["app.ini"].(string)
if !ok {
t.Fatal("missing app.ini in ConfigMap data")
}
requiredSettings := []string{
"ROOT_URL = https://spinoff.dev/forgejo/",
"HTTP_PORT = 3000",
"DB_TYPE = sqlite3",
"ENABLED = true",
"DISABLE_REGISTRATION = true",
"OFFLINE_MODE = true",
"DEFAULT_BRANCH = main",
}
for _, s := range requiredSettings {
if !strings.Contains(appIni, s) {
t.Errorf("app.ini missing required setting: %s", s)
}
}
}
func TestForgejoPVC(t *testing.T) {
docs := readForgejoDocuments(t)
doc := findDoc(docs, "PersistentVolumeClaim", "forgejo-storage")
if doc == nil {
t.Fatal("missing forgejo-storage PVC")
}
meta := doc["metadata"].(map[string]interface{})
if meta["namespace"] != "dev-infra" {
t.Errorf("expected namespace dev-infra, got %v", meta["namespace"])
}
spec := doc["spec"].(map[string]interface{})
storageClass, ok := spec["storageClassName"].(string)
if !ok || storageClass != "longhorn" {
t.Errorf("expected storageClassName longhorn, got %v", storageClass)
}
resources := spec["resources"].(map[string]interface{})
requests := resources["requests"].(map[string]interface{})
if requests["storage"] != "5Gi" {
t.Errorf("expected 5Gi storage, got %v", requests["storage"])
}
}
func TestForgejoDeployment(t *testing.T) {
docs := readForgejoDocuments(t)
doc := findDoc(docs, "Deployment", "forgejo")
if doc == nil {
t.Fatal("missing forgejo Deployment")
}
meta := doc["metadata"].(map[string]interface{})
if meta["namespace"] != "dev-infra" {
t.Errorf("expected namespace dev-infra, got %v", meta["namespace"])
}
labels := meta["labels"].(map[string]interface{})
if labels["app"] != "forgejo" {
t.Errorf("expected label app=forgejo, got %v", labels["app"])
}
spec := doc["spec"].(map[string]interface{})
strategy := spec["strategy"].(map[string]interface{})
if strategy["type"] != "Recreate" {
t.Errorf("expected Recreate strategy, got %v", strategy["type"])
}
template := spec["template"].(map[string]interface{})
podSpec := template["spec"].(map[string]interface{})
t.Run("init_containers", func(t *testing.T) {
initContainers, ok := podSpec["initContainers"].([]interface{})
if !ok || len(initContainers) < 2 {
t.Fatal("expected at least 2 init containers (setup-config and create-admin)")
}
setupC := initContainers[0].(map[string]interface{})
if setupC["name"] != "setup-config" {
t.Errorf("expected first init container name setup-config, got %v", setupC["name"])
}
initC := initContainers[1].(map[string]interface{})
if initC["name"] != "create-admin" {
t.Errorf("expected second init container name create-admin, got %v", initC["name"])
}
image, _ := initC["image"].(string)
if !strings.HasPrefix(image, "codeberg.org/forgejo/forgejo:") {
t.Errorf("expected forgejo image, got %v", image)
}
})
t.Run("containers", func(t *testing.T) {
containers := podSpec["containers"].([]interface{})
if len(containers) != 2 {
t.Fatalf("expected 2 containers (forgejo + ipip-sidecar), got %d", len(containers))
}
forgejo := containers[0].(map[string]interface{})
if forgejo["name"] != "forgejo" {
t.Errorf("expected container name forgejo, got %v", forgejo["name"])
}
image, _ := forgejo["image"].(string)
if !strings.HasPrefix(image, "codeberg.org/forgejo/forgejo:") {
t.Errorf("expected forgejo image, got %v", image)
}
ports := forgejo["ports"].([]interface{})
if len(ports) != 1 {
t.Errorf("expected 1 port, got %d", len(ports))
}
port := ports[0].(map[string]interface{})
containerPort := toInt64(port["containerPort"])
if containerPort != 3000 {
t.Errorf("expected port 3000, got %v", port["containerPort"])
}
ipip := containers[1].(map[string]interface{})
if ipip["name"] != "ipip-sidecar" {
t.Errorf("expected container name ipip-sidecar, got %v", ipip["name"])
}
})
t.Run("volumes", func(t *testing.T) {
volumes := podSpec["volumes"].([]interface{})
if len(volumes) < 2 {
t.Fatalf("expected at least 2 volumes (storage + config), got %d", len(volumes))
}
volumeNames := make(map[string]bool)
for _, v := range volumes {
vol := v.(map[string]interface{})
volumeNames[vol["name"].(string)] = true
}
if !volumeNames["storage"] {
t.Error("missing storage volume")
}
if !volumeNames["config"] {
t.Error("missing config volume")
}
})
}
func TestForgejoService(t *testing.T) {
docs := readForgejoDocuments(t)
doc := findDoc(docs, "Service", "forgejo")
if doc == nil {
t.Fatal("missing forgejo Service")
}
meta := doc["metadata"].(map[string]interface{})
if meta["namespace"] != "dev-infra" {
t.Errorf("expected namespace dev-infra, got %v", meta["namespace"])
}
spec := doc["spec"].(map[string]interface{})
if spec["type"] != "ClusterIP" {
t.Errorf("expected ClusterIP, got %v", spec["type"])
}
ports := spec["ports"].([]interface{})
if len(ports) != 1 {
t.Fatalf("expected 1 port, got %d", len(ports))
}
port := ports[0].(map[string]interface{})
portNum := toInt64(port["port"])
if portNum != 3000 {
t.Errorf("expected port 3000, got %v", port["port"])
}
selector := spec["selector"].(map[string]interface{})
if selector["app"] != "forgejo" {
t.Errorf("expected selector app=forgejo, got %v", selector["app"])
}
}
func TestForgejoIngressRoute(t *testing.T) {
docs := readForgejoDocuments(t)
doc := findDoc(docs, "IngressRoute", "forgejo-ingress")
if doc == nil {
t.Fatal("missing forgejo-ingress IngressRoute")
}
meta := doc["metadata"].(map[string]interface{})
if meta["namespace"] != "dev-infra" {
t.Errorf("expected namespace dev-infra, got %v", meta["namespace"])
}
spec := doc["spec"].(map[string]interface{})
entryPoints := spec["entryPoints"].([]interface{})
if len(entryPoints) != 2 {
t.Errorf("expected 2 entryPoints (web, websecure), got %d", len(entryPoints))
}
routes := spec["routes"].([]interface{})
if len(routes) < 2 {
t.Fatalf("expected at least 2 routes (main + trailing-slash redirect), got %d", len(routes))
}
mainRoute := routes[0].(map[string]interface{})
matchRule, _ := mainRoute["match"].(string)
if !strings.Contains(matchRule, "PathPrefix(`/forgejo/`)") {
t.Errorf("expected PathPrefix /forgejo/ in main route, got %v", matchRule)
}
middlewares := mainRoute["middlewares"].([]interface{})
if len(middlewares) < 1 {
t.Fatal("expected at least 1 middleware on main route")
}
mw := middlewares[0].(map[string]interface{})
if mw["name"] != "forgejo-basic-auth" {
t.Errorf("expected forgejo-basic-auth middleware, got %v", mw["name"])
}
}
func TestForgejoBasicAuthMiddleware(t *testing.T) {
docs := readForgejoDocuments(t)
doc := findDoc(docs, "Middleware", "forgejo-basic-auth")
if doc == nil {
t.Fatal("missing forgejo-basic-auth Middleware")
}
meta := doc["metadata"].(map[string]interface{})
if meta["namespace"] != "dev-infra" {
t.Errorf("expected namespace dev-infra, got %v", meta["namespace"])
}
spec := doc["spec"].(map[string]interface{})
basicAuth := spec["basicAuth"].(map[string]interface{})
if basicAuth["secret"] != "forgejo-basic-auth-secret" {
t.Errorf("expected secret forgejo-basic-auth-secret, got %v", basicAuth["secret"])
}
}
func TestForgejoTrailingSlashMiddleware(t *testing.T) {
docs := readForgejoDocuments(t)
doc := findDoc(docs, "Middleware", "forgejo-add-trailing-slash")
if doc == nil {
t.Fatal("missing forgejo-add-trailing-slash Middleware")
}
spec := doc["spec"].(map[string]interface{})
redirect := spec["redirectRegex"].(map[string]interface{})
if redirect["permanent"] != true {
t.Error("expected permanent redirect")
}
}