Files
2026-08-17 10:10:14 +08:00

212 lines
7.4 KiB
Go

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
}