@
Build backend images / build content-svc (push) Failing after 19s
Build backend images / build file-svc (push) Failing after 1m53s
Build backend images / build gateway (push) Failing after 16s
Build backend images / build identity-svc (push) Failing after 7m1s
Build backend images / build notification-svc (push) Failing after 7m24s
Build backend images / build render-svc (push) Failing after 3m12s
Build backend images / build studio-svc (push) Failing after 43s
Build backend images / build content-svc (push) Failing after 19s
Build backend images / build file-svc (push) Failing after 1m53s
Build backend images / build gateway (push) Failing after 16s
Build backend images / build identity-svc (push) Failing after 7m1s
Build backend images / build notification-svc (push) Failing after 7m24s
Build backend images / build render-svc (push) Failing after 3m12s
Build backend images / build studio-svc (push) Failing after 43s
feat: AE template scanner + scene editor + AEP bundle pipeline
Scene editor (admin): per-project Scenes / Shared Colors / Color Presets
manager (ProjectScenes) reachable from each project.
AEP bundle pipeline: upload .aep or .zip → stored once per template at
templates/{project_id}/(bundle.zip|template.aep); render claim probes and
returns is_bundle+md5; node-agent extracts the bundle, locates the .aep
(zip-slip guarded), and caches by md5 so repeated renders extract once.
AE template scanner ("read scenes/colours/configs from the AEP"):
- content-svc importer: POST /v1/projects/{id}/scan/{preview,apply} —
review-diff-then-merge into scenes/elements/colours (manual edits kept).
- render-svc Go quick-scan: stdlib RIFX parser extracts comp names+durations
(no AE) → POST /v1/template-scans/{id}/quick.
- render-svc AE scan jobs + node-agent runner: queue → node runs scan.jsx
(reverse of legacy JSXGenerator conventions: frfinal/frshare/frl_/frd_) →
posts ScanResult back. Migration 26_render_scan_jobs.
- admin UI: "اسکن از افترافکت" with quick/full engines + diff-review modal.
Verified: importer preview/apply, Go quick-scan end-to-end (synthetic .aep →
scene imported), bundle extract unit tests, RIFX parser unit tests.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
package runner
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// writeZip builds a zip at path from the given name→content entries (dirs implied
|
||||
// by trailing slash). Returns the path.
|
||||
func writeZip(t *testing.T, path string, entries map[string]string) {
|
||||
t.Helper()
|
||||
f, err := os.Create(path)
|
||||
if err != nil {
|
||||
t.Fatalf("create zip: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
zw := zip.NewWriter(f)
|
||||
for name, content := range entries {
|
||||
w, err := zw.Create(name)
|
||||
if err != nil {
|
||||
t.Fatalf("zip create entry %s: %v", name, err)
|
||||
}
|
||||
if _, err := w.Write([]byte(content)); err != nil {
|
||||
t.Fatalf("zip write %s: %v", name, err)
|
||||
}
|
||||
}
|
||||
if err := zw.Close(); err != nil {
|
||||
t.Fatalf("close zip: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractAndFindAEP(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
zipPath := filepath.Join(dir, "bundle.zip")
|
||||
// Realistic bundle: project + footage, plus a macOS resource-fork decoy that
|
||||
// must NOT be chosen as the project file.
|
||||
writeZip(t, zipPath, map[string]string{
|
||||
"MyTemplate/template.aep": "AEP-DATA",
|
||||
"MyTemplate/footage/clip.mp4": "VIDEO",
|
||||
"MyTemplate/fonts/Vazir.ttf": "FONT",
|
||||
"__MACOSX/MyTemplate/._template.aep": "RESOURCE-FORK",
|
||||
})
|
||||
|
||||
dest := filepath.Join(dir, "extracted")
|
||||
if err := ExtractZip(zipPath, dest); err != nil {
|
||||
t.Fatalf("ExtractZip: %v", err)
|
||||
}
|
||||
|
||||
// footage + font extracted
|
||||
if _, err := os.Stat(filepath.Join(dest, "MyTemplate", "footage", "clip.mp4")); err != nil {
|
||||
t.Errorf("footage not extracted: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(dest, "MyTemplate", "fonts", "Vazir.ttf")); err != nil {
|
||||
t.Errorf("font not extracted: %v", err)
|
||||
}
|
||||
|
||||
aep, err := FindAEP(dest)
|
||||
if err != nil {
|
||||
t.Fatalf("FindAEP: %v", err)
|
||||
}
|
||||
if filepath.Base(aep) != "template.aep" {
|
||||
t.Errorf("expected template.aep, got %s", aep)
|
||||
}
|
||||
if strings.Contains(aep, "__MACOSX") || strings.Contains(filepath.Base(aep), "._") {
|
||||
t.Errorf("FindAEP picked a macOS resource-fork decoy: %s", aep)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindAEPPrefersShallowAndAepOverAepx(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
// deep .aepx + shallow .aep — shallow .aep must win.
|
||||
mustWrite(t, filepath.Join(dir, "a", "b", "deep.aepx"), "x")
|
||||
mustWrite(t, filepath.Join(dir, "root.aep"), "x")
|
||||
aep, err := FindAEP(dir)
|
||||
if err != nil {
|
||||
t.Fatalf("FindAEP: %v", err)
|
||||
}
|
||||
if filepath.Base(aep) != "root.aep" {
|
||||
t.Errorf("expected root.aep (shallow, .aep), got %s", aep)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindAEPNoneFound(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
mustWrite(t, filepath.Join(dir, "readme.txt"), "no project here")
|
||||
if _, err := FindAEP(dir); err == nil {
|
||||
t.Error("expected error when no .aep present, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractZipRejectsZipSlip(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
zipPath := filepath.Join(dir, "evil.zip")
|
||||
writeZip(t, zipPath, map[string]string{
|
||||
"../escape.txt": "pwned",
|
||||
})
|
||||
dest := filepath.Join(dir, "extracted")
|
||||
if err := ExtractZip(zipPath, dest); err == nil {
|
||||
t.Error("expected zip-slip to be rejected, got nil error")
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(dir, "escape.txt")); err == nil {
|
||||
t.Error("zip-slip wrote a file outside the destination dir")
|
||||
}
|
||||
}
|
||||
|
||||
func mustWrite(t *testing.T, path, content string) {
|
||||
t.Helper()
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user