ci / go (push) Waiting to run
ci / go-db (agent) (push) Waiting to run
ci / go-db (config) (push) Waiting to run
ci / go-db (db) (push) Waiting to run
ci / go-db (evidence) (push) Waiting to run
ci / go-db (llmrec) (push) Waiting to run
ci / go-db (server) (push) Waiting to run
web / web (push) Waiting to run
docs / links (push) Canceled after 0s
detections / detections (push) Canceled after 0s
257 lines
8.7 KiB
Go
257 lines
8.7 KiB
Go
package server
|
|
|
|
import (
|
|
"archive/tar"
|
|
"bytes"
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
pgdb "github.com/Autumn-27/artex/db"
|
|
"github.com/klauspost/compress/zstd"
|
|
)
|
|
|
|
func TestTaskArchivePackageFilesRoundTrip(t *testing.T) {
|
|
dataDir := t.TempDir()
|
|
taskID := "42"
|
|
explorationID := int64(73)
|
|
taskFile := filepath.Join(dataDir, "tasks", taskID, "uploads", "evidence.txt")
|
|
transcriptFile := filepath.Join(dataDir, "transcripts", "exp73-worker-1.jsonl")
|
|
unrelatedTranscript := filepath.Join(dataDir, "transcripts", "exp74-worker-1.jsonl")
|
|
for path, body := range map[string]string{
|
|
taskFile: "task evidence",
|
|
transcriptFile: "transcript",
|
|
unrelatedTranscript: "leave me hot",
|
|
} {
|
|
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(path, []byte(body), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
stage, err := stageTaskArchiveFiles(dataDir, 1, taskID, explorationID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := os.Stat(taskFile); !os.IsNotExist(err) {
|
|
t.Fatalf("task file still hot after staging: %v", err)
|
|
}
|
|
if _, err := os.Stat(transcriptFile); !os.IsNotExist(err) {
|
|
t.Fatalf("task transcript still hot after staging: %v", err)
|
|
}
|
|
if _, err := os.Stat(unrelatedTranscript); err != nil {
|
|
t.Fatalf("unrelated transcript was staged: %v", err)
|
|
}
|
|
|
|
archivePath := taskArchivePath(dataDir, 1, taskID)
|
|
streamPath := filepath.Join(stage.payload, filepath.FromSlash(pgdb.TaskArchiveLLMRecordsPath))
|
|
if err := os.MkdirAll(filepath.Dir(streamPath), archiveDirMode); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
streamBody := []byte("{\"id\":1,\"raw_request\":\"large\"}\n")
|
|
if err := os.WriteFile(streamPath, streamBody, archiveFileMode); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
snapshot := &pgdb.TaskArchiveSnapshot{
|
|
FormatVersion: pgdb.TaskArchiveFormatVersion, TaskID: 42, ExplorationID: explorationID,
|
|
StreamedTables: map[string]string{"llm_records": pgdb.TaskArchiveLLMRecordsPath},
|
|
}
|
|
original, compressed, checksum, err := writeTaskArchivePackage(archivePath, stage.payload, snapshot)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if original == 0 || compressed == 0 || checksum == "" {
|
|
t.Fatalf("invalid package metrics original=%d compressed=%d checksum=%q", original, compressed, checksum)
|
|
}
|
|
if err := stage.commit(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
extracted := filepath.Join(dataDir, "restore")
|
|
if err := extractTaskArchivePackage(archivePath, checksum, extracted); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got, err := os.ReadFile(filepath.Join(extracted, filepath.FromSlash(pgdb.TaskArchiveLLMRecordsPath))); err != nil || !bytes.Equal(got, streamBody) {
|
|
t.Fatalf("streamed LLM archive payload=%q err=%v", got, err)
|
|
}
|
|
installed, err := installTaskArchiveFiles(dataDir, extracted, taskID, 1)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := installed.commit(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for path, want := range map[string]string{taskFile: "task evidence", transcriptFile: "transcript"} {
|
|
got, err := os.ReadFile(path)
|
|
if err != nil || string(got) != want {
|
|
t.Fatalf("restored %s = %q, %v; want %q", path, got, err, want)
|
|
}
|
|
}
|
|
if err := extractTaskArchivePackage(archivePath, "deadbeef", filepath.Join(dataDir, "bad-checksum")); err == nil {
|
|
t.Fatal("checksum mismatch was accepted")
|
|
}
|
|
}
|
|
|
|
func TestTaskArchivePackageSkipsSymlink(t *testing.T) {
|
|
dataDir := t.TempDir()
|
|
payload := filepath.Join(dataDir, "payload")
|
|
if err := os.MkdirAll(payload, archiveDirMode); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
regular := filepath.Join(payload, "keep.txt")
|
|
if err := os.WriteFile(regular, []byte("keep me"), archiveFileMode); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// 工作目录里出现的符号链接应被跳过,而不是让整个归档失败。
|
|
if err := os.Symlink(regular, filepath.Join(payload, "link.txt")); err != nil {
|
|
t.Skipf("symlink unsupported on this platform: %v", err)
|
|
}
|
|
|
|
archivePath := filepath.Join(dataDir, "archive.tar.zst")
|
|
snapshot := &pgdb.TaskArchiveSnapshot{FormatVersion: pgdb.TaskArchiveFormatVersion, TaskID: 42}
|
|
_, _, checksum, err := writeTaskArchivePackage(archivePath, payload, snapshot)
|
|
if err != nil {
|
|
t.Fatalf("archive should skip symlink, not fail: %v", err)
|
|
}
|
|
extracted := filepath.Join(dataDir, "restore")
|
|
if err := extractTaskArchivePackage(archivePath, checksum, extracted); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got, err := os.ReadFile(filepath.Join(extracted, "keep.txt")); err != nil || string(got) != "keep me" {
|
|
t.Fatalf("regular file not archived: got=%q err=%v", got, err)
|
|
}
|
|
if _, err := os.Lstat(filepath.Join(extracted, "link.txt")); !os.IsNotExist(err) {
|
|
t.Fatalf("symlink should have been skipped, but link.txt exists: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestTaskArchiveStageJournalRollsBackInterruptedMoves(t *testing.T) {
|
|
dataDir := t.TempDir()
|
|
taskFile := filepath.Join(dataDir, "tasks", "42", "evidence.txt")
|
|
transcriptFile := filepath.Join(dataDir, "transcripts", "exp73-worker.jsonl")
|
|
for _, path := range []string{taskFile, transcriptFile} {
|
|
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(path, []byte(path), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
stage, err := stageTaskArchiveFiles(dataDir, 9, "42", 73)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
raw, err := os.ReadFile(filepath.Join(stage.root, "journal.json"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var journal archiveStageJournal
|
|
if err := json.Unmarshal(raw, &journal); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(journal.Moves) != 2 {
|
|
t.Fatalf("journal moves=%d, want 2", len(journal.Moves))
|
|
}
|
|
recovered := &taskArchiveFileStage{root: stage.root, payload: stage.payload, journal: journal}
|
|
if err := recovered.rollback(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, path := range []string{taskFile, transcriptFile} {
|
|
if _, err := os.Stat(path); err != nil {
|
|
t.Fatalf("interrupted archive did not restore %s: %v", path, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTaskArchiveRestoreJournalRollsBackInterruptedInstall(t *testing.T) {
|
|
dataDir := t.TempDir()
|
|
extracted := filepath.Join(dataDir, "archives", "tasks", ".restore", "11-test")
|
|
source := filepath.Join(extracted, "files", "tasks", "42", "evidence.txt")
|
|
if err := os.MkdirAll(filepath.Dir(source), 0o700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(source, []byte("evidence"), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_, err := installTaskArchiveFiles(dataDir, extracted, "42", 11)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
destination := filepath.Join(dataDir, "tasks", "42", "evidence.txt")
|
|
if _, err := os.Stat(destination); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
raw, err := os.ReadFile(filepath.Join(extracted, "restore-journal.json"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var journal archiveRestoreJournal
|
|
if err := json.Unmarshal(raw, &journal); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if journal.ArchiveID != 11 || len(journal.Moves) != 1 {
|
|
t.Fatalf("unexpected restore journal: %+v", journal)
|
|
}
|
|
recovered := &taskArchiveRestoreFiles{extracted: extracted, moves: journal.Moves}
|
|
if err := recovered.rollback(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := os.Stat(destination); !os.IsNotExist(err) {
|
|
t.Fatalf("hot destination remains after restore rollback: %v", err)
|
|
}
|
|
if got, err := os.ReadFile(source); err != nil || string(got) != "evidence" {
|
|
t.Fatalf("restore source=%q err=%v", got, err)
|
|
}
|
|
}
|
|
|
|
func TestTaskArchiveDeletePackageCanResumeFromStagedPath(t *testing.T) {
|
|
dataDir := t.TempDir()
|
|
archivePath := taskArchivePath(dataDir, 21, "42")
|
|
if err := os.MkdirAll(filepath.Dir(archivePath), archiveDirMode); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
staged := archivePath + ".deleting-21"
|
|
if err := os.WriteFile(staged, []byte("archive"), archiveFileMode); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, moved, err := stageTaskArchivePackageDelete(archivePath, 21)
|
|
if err != nil || !moved || got != staged {
|
|
t.Fatalf("resume staged package path=%q moved=%v err=%v", got, moved, err)
|
|
}
|
|
}
|
|
|
|
func TestTaskArchivePackageRejectsTraversal(t *testing.T) {
|
|
var compressed bytes.Buffer
|
|
encoder, err := zstd.NewWriter(&compressed)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
tw := tar.NewWriter(encoder)
|
|
content := []byte("escape")
|
|
if err := tw.WriteHeader(&tar.Header{Name: "../escape.txt", Mode: 0o600, Size: int64(len(content))}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := tw.Write(content); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := tw.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := encoder.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
path := filepath.Join(t.TempDir(), "malicious.tar.zst")
|
|
if err := os.WriteFile(path, compressed.Bytes(), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
root := t.TempDir()
|
|
if err := extractTaskArchivePackage(path, "", root); err == nil {
|
|
t.Fatal("path traversal archive was accepted")
|
|
}
|
|
if _, err := os.Stat(filepath.Join(filepath.Dir(root), "escape.txt")); !os.IsNotExist(err) {
|
|
t.Fatalf("traversal wrote outside destination: %v", err)
|
|
}
|
|
}
|