package workflowpackage import ( "archive/zip" "bytes" "strings" "testing" "time" ) func testDocument() Document { return Document{ ID: "web-src-hunting", Name: "Web SRC 猎洞", Description: "面向 SRC Web 资产的侦察与漏洞候选流程", Version: 18, Enabled: true, GraphJSON: `{"nodes":[{"id":"start-1","type":"start","label":"开始","position":{"x":0,"y":0},"config":{}},{"id":"out-1","type":"output","label":"输出","position":{"x":0,"y":120},"config":{"output_key":"result","source_binding":{"from":"inputs","field":"message"}}}],"edges":[{"id":"e1","source":"start-1","target":"out-1"}],"config":{"schema_version":1}}`, UpdatedAt: time.Date(2026, 7, 13, 10, 0, 0, 0, time.UTC), } } func TestExportIsDeterministicAndSelfDescribing(t *testing.T) { first, firstMeta, err := Export(testDocument()) if err != nil { t.Fatalf("first export: %v", err) } second, secondMeta, err := Export(testDocument()) if err != nil { t.Fatalf("second export: %v", err) } if !bytes.Equal(first, second) { t.Fatal("identical document must produce byte-identical package") } if firstMeta.PackageHash != secondMeta.PackageHash || !strings.HasPrefix(firstMeta.PackageHash, "sha256:") { t.Fatalf("unexpected deterministic package hash: %#v / %#v", firstMeta, secondMeta) } zr, err := zip.NewReader(bytes.NewReader(first), int64(len(first))) if err != nil { t.Fatalf("open package: %v", err) } if len(zr.File) != 3 { t.Fatalf("zip entry count = %d, want 3", len(zr.File)) } wantNames := []string{"checksums.sha256", "manifest.json", "workflows/web-src-hunting.json"} for i, f := range zr.File { if f.Name != wantNames[i] { t.Fatalf("entry %d = %q, want %q", i, f.Name, wantNames[i]) } } if firstMeta.SourceRevision != 18 { t.Fatalf("source revision = %d, want 18", firstMeta.SourceRevision) } if !strings.HasPrefix(firstMeta.ContentHash, "sha256:") || !strings.HasPrefix(firstMeta.GraphHash, "sha256:") { t.Fatalf("content/graph hashes must be sha256: %#v", firstMeta) } }