// Package scripts prüft die Vollständigkeit der Auslieferung (Phase 23). // // Der Test läuft im gewöhnlichen Testlauf mit und braucht kein gebautes Paket: // Er prüft die **Bestandteile**, aus denen es entsteht. Ein Paket zu bauen, // nur um es zu prüfen, dauerte Minuten und liefe deshalb bald nicht mehr mit. package scripts import ( "os" "path/filepath" "strings" "testing" ) // repositoryRoot ist der Pfad zum Wurzelverzeichnis. const repositoryRoot = ".." // TestReleaseDocumentationIsComplete prueft die Liste aus §25. // // Der Umsetzungsplan nennt zwoelf Bestandteile, die ein Auslieferungspaket // enthalten muss. Fuenf davon sind Dokumente, und die schreibt man einmal — // danach faellt es niemandem auf, wenn eines beim Umbenennen verlorengeht. // Genau deshalb steht die Liste hier und nicht nur in einer Aufzaehlung. func TestReleaseDocumentationIsComplete(testInstance *testing.T) { requiredDocuments := map[string]string{ "docs/installation.md": "Installationsanleitung", "docs/recovery-runbook.md": "Wiederherstellungsdokumentation", "docs/security-guide.md": "Sicherheitsleitfaden", "docs/api.md": "API-Dokumentation", "docs/troubleshooting.md": "Stoerungsleitfaden", "docs/agent-installation.md": "Installationsanleitung der Agenten", "CHANGELOG.md": "Aenderungsliste", "README.md": "Ueberblick", } for documentPath, documentPurpose := range requiredDocuments { fileInformation, statError := os.Stat(filepath.Join(repositoryRoot, documentPath)) if statError != nil { testInstance.Errorf("%s fehlt (%s): %v", documentPath, documentPurpose, statError) continue } // Eine leere Datei erfüllt die Liste formal und hilft niemandem. Die // Grenze ist bewusst niedrig — sie fängt die versehentlich angelegte // Hülle, nicht ein knappes Dokument. if fileInformation.Size() < 500 { testInstance.Errorf("%s ist mit %d Byte zu klein für %s", documentPath, fileInformation.Size(), documentPurpose) } } } // TestReleaseScriptCoversEveryCommand prueft, dass kein Programm fehlt. // // Ein Programm, das gebaut, aber nicht ausgeliefert wird, faellt erst beim // Kunden auf — und dann fehlt genau das Werkzeug, das er gerade braucht. func TestReleaseScriptCoversEveryCommand(testInstance *testing.T) { buildScript := readReleaseScript(testInstance) commandDirectories := []string{"apps/api/cmd", "apps/agent/cmd"} for _, commandDirectory := range commandDirectories { directoryEntries, readError := os.ReadDir(filepath.Join(repositoryRoot, commandDirectory)) if readError != nil { testInstance.Fatalf("%s ließ sich nicht lesen: %v", commandDirectory, readError) } for _, singleEntry := range directoryEntries { if !singleEntry.IsDir() { continue } commandPath := commandDirectory + "/" + singleEntry.Name() if !strings.Contains(buildScript, commandPath) { testInstance.Errorf("das Programm %s wird gebaut, aber nicht ausgeliefert.\n"+ " Entweder in scripts/build-release.sh aufnehmen oder begründen, "+ "warum es nicht ins Paket gehört.", singleEntry.Name()) } } } } // TestReleaseScriptShipsEveryTargetPlatform prueft die Zielplattformen. // // Dieselben drei, die `make cross-build` bei jedem Lauf uebersetzt. Eine // Plattform, die uebersetzt, aber nicht ausgeliefert wird, ist eine Zusage ohne // Gegenstueck. func TestReleaseScriptShipsEveryTargetPlatform(testInstance *testing.T) { buildScript := readReleaseScript(testInstance) requiredPlatforms := []string{"linux/amd64", "linux/arm64", "windows/amd64"} for _, platformName := range requiredPlatforms { if !strings.Contains(buildScript, platformName) { testInstance.Errorf("die Zielplattform %s wird nicht ausgeliefert", platformName) } } } // TestReleaseScriptIncludesMigrationsAndInterface prueft die uebrigen Bestandteile. func TestReleaseScriptIncludesMigrationsAndInterface(testInstance *testing.T) { buildScript := readReleaseScript(testInstance) requiredParts := map[string]string{ "migrations/*.sql": "Migrationsdateien", "migrations/checksums.txt": "Prüfsummen der Migrationen", "apps/web/dist": "Weboberfläche", "docs/": "Dokumentation", "CHANGELOG.md": "Änderungsliste", "SHA256SUMS": "Prüfsummen des Pakets", } for partPattern, partPurpose := range requiredParts { if !strings.Contains(buildScript, partPattern) { testInstance.Errorf("im Auslieferungsskript fehlt %s (%s)", partPattern, partPurpose) } } // Ohne diesen Schalter bindet Go gegen die libc des Bausystems. Der Start // scheitert dann auf einer älteren Distribution mit einer Meldung über // GLIBC — die niemand einem Backupprogramm zuordnet. if !strings.Contains(buildScript, "CGO_ENABLED=0") { testInstance.Error("die Programme werden nicht statisch gebunden (CGO_ENABLED=0 fehlt)") } } // readReleaseScript liest das Auslieferungsskript. func readReleaseScript(testInstance *testing.T) string { testInstance.Helper() scriptContent, readError := os.ReadFile(filepath.Join(repositoryRoot, "scripts/build-release.sh")) if readError != nil { testInstance.Fatalf("das Auslieferungsskript ließ sich nicht lesen: %v", readError) } return string(scriptContent) }