Files

226 lines
7.2 KiB
Go

package automation
import (
"archive/zip"
"bytes"
"os"
"path/filepath"
"sort"
"strings"
"testing"
)
func TestWriteScriptPackageZipRoundTripsAdditionalFiles(t *testing.T) {
zipPath := filepath.Join(t.TempDir(), "demo-package.zip")
if err := WriteScriptPackageZip(zipPath, ImportedBundle{
Record: ScriptRecord{
ID: "zip-roundtrip",
Name: "ZIP 导出",
Description: "包含额外文件",
Type: "playwright-cdp",
Status: "ready",
EntryFile: "scripts/index.cjs",
SelectorText: `{"code":"ZIP_DEMO"}`,
ParamsText: `{"url":"https://example.com"}`,
ScriptText: "const helper = require('./helpers/helper.cjs')\nmodule.exports.run = async () => helper.run()",
},
Files: []ImportedBundleFile{
{
Path: "scripts/index.cjs",
Content: []byte("const helper = require('./helpers/helper.cjs')\nmodule.exports.run = async () => helper.run()"),
},
{
Path: "scripts/helpers/helper.cjs",
Content: []byte("module.exports.run = async () => ({ ok: true })"),
},
{
Path: "assets/raw.bin",
Content: []byte{0x00, 0x01, 0x02, 0xff},
},
},
}); err != nil {
t.Fatalf("WriteScriptPackageZip returned error: %v", err)
}
reader, err := zip.OpenReader(zipPath)
if err != nil {
t.Fatalf("open zip failed: %v", err)
}
defer reader.Close()
if !zipContainsEntry(reader.File, scriptPackageManifestName) {
t.Fatalf("expected %s in zip", scriptPackageManifestName)
}
imported, err := ImportBundleFromZip(zipPath, "本地文件 "+zipPath)
if err != nil {
t.Fatalf("ImportBundleFromZip returned error: %v", err)
}
if imported.Record.EntryFile != "scripts/index.cjs" {
t.Fatalf("unexpected entry file: %s", imported.Record.EntryFile)
}
if imported.Record.Source.Type != "local-file" {
t.Fatalf("unexpected source: %+v", imported.Record.Source)
}
if !hasBundleFile(imported.Files, "scripts/helpers/helper.cjs", []byte("module.exports.run = async () => ({ ok: true })")) {
t.Fatalf("expected helper file to round-trip, got %+v", imported.Files)
}
if !hasBundleFile(imported.Files, "assets/raw.bin", []byte{0x00, 0x01, 0x02, 0xff}) {
t.Fatalf("expected binary file to round-trip, got %+v", imported.Files)
}
}
func TestImportBundleFromBytesSupportsZipPackage(t *testing.T) {
zipData := buildScriptPackageZipBytes(t, map[string]string{
scriptPackageManifestName: `{
"name": "远程 ZIP 脚本",
"type": "playwright-cdp",
"entryFile": "scripts/index.cjs"
}`,
"scripts/index.cjs": "module.exports.run = async () => ({ ok: true, source: 'zip-bytes' })",
})
bundle, err := ImportBundleFromBytes("remote-package.zip", zipData, "远程地址 https://example.com/demo-package.zip")
if err != nil {
t.Fatalf("ImportBundleFromBytes returned error: %v", err)
}
if bundle.Record.Name != "远程 ZIP 脚本" {
t.Fatalf("unexpected script name: %s", bundle.Record.Name)
}
if bundle.Record.Source.Type != "remote-url" {
t.Fatalf("unexpected source: %+v", bundle.Record.Source)
}
if !strings.Contains(bundle.Record.ScriptText, "zip-bytes") {
t.Fatalf("unexpected script text: %s", bundle.Record.ScriptText)
}
}
func TestImportBundleFromZipPreservesPackageIdentityAndPublicAPI(t *testing.T) {
zipPath := filepath.Join(t.TempDir(), "proton-package.zip")
if err := os.WriteFile(zipPath, buildScriptPackageZipBytes(t, map[string]string{
scriptPackageManifestName: `{
"format": "ant-automation-script",
"packageFormat": "ant-automation-script",
"manifestVersion": 1,
"id": "proton-mail-first-message",
"name": "Proton 邮件搜索并读取最新邮件",
"type": "playwright-cdp",
"status": "ready",
"entryFile": "index.cjs",
"targetConfig": {
"mode": "manual"
},
"publicAPI": {
"enabled": true,
"method": "POST",
"path": "mail/proton-first-message",
"requestMode": "params-only",
"responseMode": "envelope",
"timeoutMs": 120000
}
}`,
"index.cjs": "module.exports.run = async () => ({ ok: true })",
}), 0o644); err != nil {
t.Fatalf("write zip failed: %v", err)
}
bundle, err := ImportBundleFromZip(zipPath, "本地文件 "+zipPath)
if err != nil {
t.Fatalf("ImportBundleFromZip returned error: %v", err)
}
if bundle.Record.ID != "proton-mail-first-message" {
t.Fatalf("expected package id to round-trip, got %q", bundle.Record.ID)
}
if bundle.Record.Status != "ready" {
t.Fatalf("expected package status to round-trip, got %q", bundle.Record.Status)
}
if !bundle.Record.PublicAPI.Enabled || bundle.Record.PublicAPI.Path != "mail/proton-first-message" {
t.Fatalf("expected public api to round-trip, got %+v", bundle.Record.PublicAPI)
}
if bundle.Record.TargetConfig.Mode != "manual" {
t.Fatalf("expected target config to round-trip, got %+v", bundle.Record.TargetConfig)
}
}
func TestImportBundleFromZipSupportsSingleRootDirectory(t *testing.T) {
zipPath := filepath.Join(t.TempDir(), "nested.zip")
if err := os.WriteFile(zipPath, buildScriptPackageZipBytes(t, map[string]string{
"demo/automation.script.json": `{
"name": "单根目录 ZIP",
"type": "playwright-cdp",
"entryFile": "scripts/index.cjs"
}`,
"demo/scripts/index.cjs": "const helper = require('./helpers/helper.cjs')\nmodule.exports.run = async () => helper.run()",
"demo/scripts/helpers/helper.cjs": "module.exports.run = async () => ({ ok: true })",
"__MACOSX/demo/._index.cjs": "ignored",
"demo/.DS_Store": "ignored",
}), 0o644); err != nil {
t.Fatalf("write nested zip failed: %v", err)
}
bundle, err := ImportBundleFromZip(zipPath, "本地文件 "+zipPath)
if err != nil {
t.Fatalf("ImportBundleFromZip returned error: %v", err)
}
if bundle.Record.Name != "单根目录 ZIP" {
t.Fatalf("unexpected script name: %s", bundle.Record.Name)
}
if !hasBundleFile(bundle.Files, "scripts/helpers/helper.cjs", []byte("module.exports.run = async () => ({ ok: true })")) {
t.Fatalf("expected nested helper file, got %+v", bundle.Files)
}
}
func TestImportBundleFromZipRejectsZipSlip(t *testing.T) {
zipPath := filepath.Join(t.TempDir(), "zip-slip.zip")
if err := os.WriteFile(zipPath, buildScriptPackageZipBytes(t, map[string]string{
"../evil.cjs": "module.exports.run = async () => ({ ok: false })",
}), 0o644); err != nil {
t.Fatalf("write zip failed: %v", err)
}
if _, err := ImportBundleFromZip(zipPath, "本地文件 "+zipPath); err == nil || !strings.Contains(err.Error(), "invalid path") {
t.Fatalf("expected zip slip error, got %v", err)
}
}
func buildScriptPackageZipBytes(t *testing.T, files map[string]string) []byte {
t.Helper()
var buf bytes.Buffer
writer := zip.NewWriter(&buf)
paths := make([]string, 0, len(files))
for relativePath := range files {
paths = append(paths, relativePath)
}
sort.Strings(paths)
for _, relativePath := range paths {
entry, err := writer.Create(relativePath)
if err != nil {
t.Fatalf("create zip entry failed: %v", err)
}
if _, err := entry.Write([]byte(files[relativePath])); err != nil {
t.Fatalf("write zip entry failed: %v", err)
}
}
if err := writer.Close(); err != nil {
t.Fatalf("close zip writer failed: %v", err)
}
return buf.Bytes()
}
func zipContainsEntry(files []*zip.File, target string) bool {
for _, file := range files {
if file.Name == target {
return true
}
}
return false
}