package bundle import ( "archive/zip" "bytes" "encoding/json" "os" "path/filepath" "strings" "testing" ) const examples = "../../../contract/examples/bundles" func TestReadExamples(t *testing.T) { entries, err := os.ReadDir(examples) if err != nil { t.Fatal(err) } for _, e := range entries { if _, err := Read(filepath.Join(examples, e.Name())); err != nil { t.Errorf("%s: %v", e.Name(), err) } } } func zipWith(t *testing.T, instructions map[string]any, files map[string]string) []byte { t.Helper() var buffer bytes.Buffer z := zip.NewWriter(&buffer) raw, _ := json.Marshal(instructions) files[InstructionsFile] = string(raw) for name, content := range files { w, _ := z.Create(name) w.Write([]byte(content)) } z.Close() return buffer.Bytes() } func instructions(ops ...map[string]any) map[string]any { return map[string]any{"format": 1, "generated": "2026-10-18T12:00:00Z", "tools": []string{"claude"}, "packages": []any{}, "operations": ops, "hints": []any{}} } func install(pkg, version, path, source, sha string) map[string]any { return map[string]any{"op": "install", "package": pkg, "version": version, "checksum": sha, "runtime": "python", "entry": "x", "files": []any{map[string]any{"path": path, "source": source, "sha256": sha}}} } func TestInvalidBundles(t *testing.T) { content := "x" sha := Sha256([]byte(content)) tests := []struct { name string zip []byte message string }{ {"Pfad nach draußen", zipWith(t, instructions(map[string]any{"op": "file", "package": "pub-x", "path": "../x", "source": "files/x", "sha256": sha}), map[string]string{"files/x": content}), "Segment"}, {"absoluter Pfad", zipWith(t, instructions(map[string]any{"op": "remove", "package": "pub-x", "path": "/etc/passwd"}), map[string]string{}), "relative"}, {"Windows-Pfad", zipWith(t, instructions(map[string]any{"op": "remove", "package": "pub-x", "path": `C:\x`}), map[string]string{}), "relative"}, {"falsche Prüfsumme", zipWith(t, instructions(map[string]any{"op": "file", "package": "pub-x", "path": "x", "source": "files/x", "sha256": Sha256([]byte("y"))}), map[string]string{"files/x": content}), "Prüfsumme"}, {"Quelle fehlt", zipWith(t, instructions(map[string]any{"op": "file", "package": "pub-x", "path": "x", "source": "files/x", "sha256": sha}), map[string]string{}), "fehlt"}, {"Quelle außerhalb files/", zipWith(t, instructions(map[string]any{"op": "file", "package": "pub-x", "path": "x", "source": "x", "sha256": sha}), map[string]string{"x": content}), "files/"}, {"unbekannte Operation", zipWith(t, instructions(map[string]any{"op": "ausfuehren"}), map[string]string{}), "unbekannte"}, {"lokaler Server: Paket als Pfad", zipWith(t, instructions(install("../x", "1.0.0", "x", "local/../x/1.0.0/x", sha)), map[string]string{}), "ungültiges Paket"}, {"lokaler Server: Version als Pfad", zipWith(t, instructions(install("pub-x", "..", "x", "local/pub-x/../x", sha)), map[string]string{}), "ungültiges Paket"}, {"lokaler Server: Datei nach draußen", zipWith(t, instructions(install("pub-x", "1.0.0", "../x", "local/pub-x/1.0.0/../x", sha)), map[string]string{}), "Segment"}, {"lokaler Server: Quelle passt nicht", zipWith(t, instructions(install("pub-x", "1.0.0", "x", "local/pub-y/1.0.0/x", sha)), map[string]string{"local/pub-y/1.0.0/x": content}), "passt nicht"}, {"lokaler Server: falsche Prüfsumme", zipWith(t, instructions(install("pub-x", "1.0.0", "x", "local/pub-x/1.0.0/x", Sha256([]byte("y")))), map[string]string{"local/pub-x/1.0.0/x": content}), "Prüfsumme"}, {"neues Format", zipWith(t, map[string]any{"format": 2}, map[string]string{}), "Format"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { _, err := FromZip(tt.zip) if err == nil || !strings.Contains(err.Error(), tt.message) { t.Errorf("Fehler %v, erwartet %q", err, tt.message) } }) } }