package updatepackage import ( "archive/zip" "bytes" "crypto/sha256" "encoding/hex" "encoding/json" "os" "path/filepath" "strings" "testing" ) // TestOpenAndExtractNativeBackend 验证正常流程:打开合法的原生后端更新包后, // 元数据与身份正确,且 ExtractArtifact 提取出的内容与原始 JAR 完全一致。 func TestOpenAndExtractNativeBackend(t *testing.T) { jar := []byte("native backend JAR") packagePath := writeBackendPackage(t, jar, nil) updatePackage, err := OpenBackendNative(packagePath) if err != nil { t.Fatalf("open native backend package: %v", err) } defer updatePackage.Close() if updatePackage.CustomerCode != "customer-01" || updatePackage.VersionID != "V1.1.8" || updatePackage.ArtifactID != 42 { t.Fatalf("unexpected package metadata: %+v", updatePackage) } if updatePackage.Identity.Size != int64(len(jar)) || updatePackage.PackageSHA256 == "" { t.Fatalf("unexpected package identity: package=%s artifact=%+v", updatePackage.PackageSHA256, updatePackage.Identity) } destination := filepath.Join(t.TempDir(), "incoming", "backend.jar") if err := updatePackage.ExtractArtifact(destination); err != nil { t.Fatalf("extract native backend artifact: %v", err) } actual, err := os.ReadFile(destination) if err != nil || !bytes.Equal(actual, jar) { t.Fatalf("unexpected extracted artifact: content=%q err=%v", actual, err) } } // TestOpenRejectsUnknownManifestField 验证清单中出现大小写错误的未知字段时, // OpenBackendNative 会返回包含该字段名的错误。 func TestOpenRejectsUnknownManifestField(t *testing.T) { jar := []byte("native backend JAR") packagePath := writeBackendPackage(t, jar, func(manifest map[string]any) { manifest["VersionID"] = "wrong-case" }) _, err := OpenBackendNative(packagePath) if err == nil || !strings.Contains(err.Error(), "VersionID") { t.Fatalf("expected unknown manifest field rejection, got %v", err) } } // TestOpenRejectsMissingDeclaredArtifact 验证清单声明的 fileName 在 ZIP 中缺失时, // OpenBackendNative 会返回包含该文件名的错误。 func TestOpenRejectsMissingDeclaredArtifact(t *testing.T) { jar := []byte("native backend JAR") packagePath := writePackageEntries(t, map[string][]byte{ manifestName: manifestJSON(t, jar, func(manifest map[string]any) { artifacts := manifest["backendArtifacts"].([]any) artifacts[0].(map[string]any)["fileName"] = "missing.jar" }), }) _, err := OpenBackendNative(packagePath) if err == nil || !strings.Contains(err.Error(), "missing.jar") { t.Fatalf("expected missing artifact rejection, got %v", err) } } // TestExtractRejectsArtifactDigestMismatch 验证清单中的 sha256 与实际工件不符时, // ExtractArtifact 返回摘要不匹配错误,且不会在目标路径留下任何已发布文件。 func TestExtractRejectsArtifactDigestMismatch(t *testing.T) { jar := []byte("native backend JAR") packagePath := writeBackendPackage(t, jar, func(manifest map[string]any) { artifacts := manifest["backendArtifacts"].([]any) artifacts[0].(map[string]any)["sha256"] = strings.Repeat("0", 64) }) updatePackage, err := OpenBackendNative(packagePath) if err != nil { t.Fatalf("open package before digest verification: %v", err) } defer updatePackage.Close() destination := filepath.Join(t.TempDir(), "backend.jar") if err := updatePackage.ExtractArtifact(destination); err == nil || !strings.Contains(err.Error(), "SHA-256 mismatch") { t.Fatalf("expected artifact digest mismatch, got %v", err) } if _, err := os.Stat(destination); !os.IsNotExist(err) { t.Fatalf("digest mismatch published artifact: %v", err) } } // TestOpenRejectsUnsafeAndDuplicateEntries 验证 OpenBackendNative 会拒绝包含 // 不安全路径条目(如 "../" 前缀)的更新包,以及包含重复 ZIP 条目的更新包。 func TestOpenRejectsUnsafeAndDuplicateEntries(t *testing.T) { jar := []byte("native backend JAR") manifest := manifestJSON(t, jar, nil) t.Run("unsafe", func(t *testing.T) { packagePath := writePackageEntries(t, map[string][]byte{ manifestName: manifest, "backend.jar": jar, "../escape": []byte("escape"), }) if _, err := OpenBackendNative(packagePath); err == nil { t.Fatal("expected unsafe ZIP entry rejection") } }) t.Run("duplicate", func(t *testing.T) { path := filepath.Join(t.TempDir(), "package.zip") file, err := os.Create(path) if err != nil { t.Fatalf("create duplicate package: %v", err) } writer := zip.NewWriter(file) for _, content := range [][]byte{manifest, manifest} { entry, err := writer.Create(manifestName) if err != nil { t.Fatalf("create duplicate manifest entry: %v", err) } if _, err := entry.Write(content); err != nil { t.Fatalf("write duplicate manifest entry: %v", err) } } if err := writer.Close(); err != nil { t.Fatalf("close duplicate package writer: %v", err) } if err := file.Close(); err != nil { t.Fatalf("close duplicate package: %v", err) } if _, err := OpenBackendNative(path); err == nil || !strings.Contains(err.Error(), "duplicate") { t.Fatalf("expected duplicate ZIP entry rejection, got %v", err) } }) } // writeBackendPackage 构造一个包含清单与 backend.jar 的合法更新包, // 返回其临时文件路径。modify 可选,用于在写入前改写清单内容。 func writeBackendPackage(t *testing.T, jar []byte, modify func(map[string]any)) string { t.Helper() return writePackageEntries(t, map[string][]byte{ manifestName: manifestJSON(t, jar, modify), "backend.jar": jar, }) } // manifestJSON 构造一份以 jar 摘要为 sha256 的完整清单 JSON,返回序列化字节。 // modify 可选,用于在序列化前改写清单内容。 func manifestJSON(t *testing.T, jar []byte, modify func(map[string]any)) []byte { t.Helper() digest := sha256.Sum256(jar) manifest := map[string]any{ "customerCode": "customer-01", "customerDisplayName": "Customer 01", "versionId": "V1.1.8", "items": []string{"deploy-sync.sh"}, "backendArtifacts": []any{map[string]any{ "id": int64(42), "versionCode": "V1.1.8", "artifactKind": backendArtifactKind, "type": nativeArtifactType, "selectedType": nativeArtifactType, "platform": nil, "fileName": "backend.jar", "filePath": "/archive/backend.jar", "sha256": hex.EncodeToString(digest[:]), "imageRef": nil, }}, "frontendArtifacts": []any{}, "nodeSsrArtifacts": []any{}, "remark": nil, } if modify != nil { modify(manifest) } content, err := json.Marshal(manifest) if err != nil { t.Fatalf("encode manifest: %v", err) } return content } // writePackageEntries 将给定名字到内容的映射写入一个临时 ZIP 文件, // 返回该文件路径。映射遍历顺序不影响结果。 func writePackageEntries(t *testing.T, entries map[string][]byte) string { t.Helper() packagePath := filepath.Join(t.TempDir(), "package.zip") file, err := os.Create(packagePath) if err != nil { t.Fatalf("create package: %v", err) } writer := zip.NewWriter(file) for name, content := range entries { entry, err := writer.Create(name) if err != nil { t.Fatalf("create package entry %s: %v", name, err) } if _, err := entry.Write(content); err != nil { t.Fatalf("write package entry %s: %v", name, err) } } if err := writer.Close(); err != nil { t.Fatalf("close package writer: %v", err) } if err := file.Close(); err != nil { t.Fatalf("close package: %v", err) } return packagePath }