repositories / bugabinga.net
bugabinga.net
personal infrastructure for bugabinga!
owned by admin
services/luci/internal/artifact/artifact_test.go
Rawpackage artifact
import (
"errors"
"fmt"
"os"
"path/filepath"
"testing"
"bugabinga.net/luci/internal/ciconfig"
)
func TestCollectStagesImmutableFiles(t *testing.T) {
root, workspace := t.TempDir(), t.TempDir()
if err := os.MkdirAll(filepath.Join(workspace, "dist"), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(workspace, "dist", "result.txt"), []byte("ok"), 0o644); err != nil {
t.Fatal(err)
}
store := Store{DataDir: root}
if err := store.Collect(workspace, "123", "001", ciconfig.Artifact{Name: "build", Paths: []string{"dist"}}); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(workspace, "dist", "result.txt"), []byte("changed"), 0o644); err != nil {
t.Fatal(err)
}
file, err := store.Open("123", "001/build/dist/result.txt")
if err != nil {
t.Fatal(err)
}
defer file.Close()
data, err := os.ReadFile(file.Name())
if err != nil {
t.Fatal(err)
}
if string(data) != "ok" {
t.Fatalf("stored = %q", data)
}
if _, err := store.Open("123", "../build"); !errors.Is(err, os.ErrNotExist) {
t.Fatalf("traversal error = %v", err)
}
}
func TestCollectRequiredMissingAndOversizedFilesStayInvisible(t *testing.T) {
root, workspace := t.TempDir(), t.TempDir()
store := Store{DataDir: root}
if err := store.Collect(workspace, "123", "001", ciconfig.Artifact{Name: "missing", Paths: []string{"none"}, Required: true}); err == nil {
t.Fatal("missing required artifact accepted")
}
if err := os.WriteFile(filepath.Join(workspace, "large"), make([]byte, maxFile+1), 0o644); err != nil {
t.Fatal(err)
}
if err := store.Collect(workspace, "123", "001", ciconfig.Artifact{Name: "large", Paths: []string{"large"}}); err == nil {
t.Fatal("large artifact accepted")
}
for _, name := range []string{"missing", "large"} {
if _, err := os.Stat(filepath.Join(root, "artifacts", "123", "001", name)); !errors.Is(err, os.ErrNotExist) {
t.Fatalf("partial %q visible: %v", name, err)
}
}
}
func TestCollectBoundsFilesAcrossSets(t *testing.T) {
root, workspace := t.TempDir(), t.TempDir()
for i := 0; i < maxFiles; i++ {
name := filepath.Join(workspace, fmt.Sprintf("file-%03d", i))
if err := os.WriteFile(name, []byte("x"), 0o644); err != nil {
t.Fatal(err)
}
}
store := Store{DataDir: root}
paths := make([]string, maxFiles)
for i := range paths {
paths[i] = fmt.Sprintf("file-%03d", i)
}
if err := store.Collect(workspace, "123", "001", ciconfig.Artifact{Name: "first", Paths: paths}); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(workspace, "extra"), []byte("x"), 0o644); err != nil {
t.Fatal(err)
}
if err := store.Collect(workspace, "123", "001", ciconfig.Artifact{Name: "second", Paths: []string{"extra"}}); err == nil {
t.Fatal("child file budget exceeded")
}
if _, err := os.Stat(filepath.Join(root, "artifacts", "123", "001", "second")); !errors.Is(err, os.ErrNotExist) {
t.Fatalf("over-budget set visible: %v", err)
}
}
func TestListAndOpenHideIncompleteStage(t *testing.T) {
root := t.TempDir()
stage := filepath.Join(root, "artifacts", "123", "001", ".stage-copy")
if err := os.MkdirAll(stage, 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(stage, "private.txt"), []byte("private"), 0o644); err != nil {
t.Fatal(err)
}
store := Store{DataDir: root}
files, err := store.List("123", "")
if err != nil {
t.Fatal(err)
}
if len(files) != 0 {
t.Fatalf("staged files listed: %#v", files)
}
if _, err := store.Open("123", "001/.stage-copy/private.txt"); !errors.Is(err, os.ErrNotExist) {
t.Fatalf("staged file opened: %v", err)
}
}
func TestCollectRejectsSourceAncestorSymlink(t *testing.T) {
root, workspace, outside := t.TempDir(), t.TempDir(), t.TempDir()
if err := os.WriteFile(filepath.Join(outside, "secret"), []byte("private"), 0o600); err != nil {
t.Fatal(err)
}
if err := os.Symlink(outside, filepath.Join(workspace, "dist")); err != nil {
t.Fatal(err)
}
store := Store{DataDir: root}
if err := store.Collect(workspace, "123", "001", ciconfig.Artifact{Name: "set", Paths: []string{"dist/secret"}, Required: true}); err == nil {
t.Fatal("ancestor symlink accepted")
}
if _, err := store.Open("123", "001/set/dist/secret"); !errors.Is(err, os.ErrNotExist) {
t.Fatalf("leaked artifact opened: %v", err)
}
}
func TestOpenRejectsStorageAncestorSymlink(t *testing.T) {
root, outside := t.TempDir(), t.TempDir()
if err := os.WriteFile(filepath.Join(outside, "secret"), []byte("private"), 0o600); err != nil {
t.Fatal(err)
}
if err := os.MkdirAll(filepath.Join(root, "artifacts", "123"), 0o755); err != nil {
t.Fatal(err)
}
if err := os.Symlink(outside, filepath.Join(root, "artifacts", "123", "001")); err != nil {
t.Fatal(err)
}
if _, err := (Store{DataDir: root}).Open("123", "001/secret"); !errors.Is(err, os.ErrNotExist) {
t.Fatalf("ancestor symlink opened: %v", err)
}
}
func TestCollectRejectsSymlinkWithoutPartialVisibility(t *testing.T) {
root, workspace := t.TempDir(), t.TempDir()
if err := os.Symlink("/etc/passwd", filepath.Join(workspace, "leak")); err != nil {
t.Fatal(err)
}
store := Store{DataDir: root}
err := store.Collect(workspace, "123", "001", ciconfig.Artifact{Name: "set", Paths: []string{"leak"}, Required: true})
if err == nil {
t.Fatal("symlink accepted")
}
if _, err := os.Stat(filepath.Join(root, "artifacts", "123", "001", "set")); !errors.Is(err, os.ErrNotExist) {
t.Fatalf("partial set visible: %v", err)
}
}