Remove retired lock authorities
This commit is contained in:
@@ -3,13 +3,11 @@ package app
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -626,123 +624,6 @@ func TestExecuteLocksListIsSortedAndRejectsMalformedObjects(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMutateRemoteLockStoreRetainsConcurrentUpdates(t *testing.T) {
|
||||
cfg := &config.Config{
|
||||
Pipeline: &config.PipelineConfig{Storage: config.StorageConfig{S3: &config.StorageS3Config{Bucket: "bucket", RootPrefix: "root"}}},
|
||||
Session: &config.SessionConfig{Campaign: "campaign", SessionID: "session"},
|
||||
}
|
||||
fake := &storage.FakeBackend{}
|
||||
arrived := make(chan struct{}, 2)
|
||||
release := make(chan struct{})
|
||||
var hookMu sync.Mutex
|
||||
hookCalls := 0
|
||||
fake.UploadHook = func(storage.FakeUploadCall) error {
|
||||
hookMu.Lock()
|
||||
hookCalls++
|
||||
call := hookCalls
|
||||
hookMu.Unlock()
|
||||
if call <= 2 {
|
||||
arrived <- struct{}{}
|
||||
<-release
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
mutate := func(source string) error {
|
||||
return mutateRemoteLockStore(context.Background(), cfg, fake, func(lockStore *config.PublishLockStore) error {
|
||||
set := lockSourceSet(lockStore.Locks)
|
||||
set[source] = config.PublishLockRule{Source: source}
|
||||
lockStore.Locks = lockMapValues(set)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
errs := make(chan error, 2)
|
||||
go func() { errs <- mutate("narratio.transcript.final") }()
|
||||
go func() { errs <- mutate("narratio.transcript.final_trimmed") }()
|
||||
<-arrived
|
||||
<-arrived
|
||||
close(release)
|
||||
if err := <-errs; err != nil {
|
||||
t.Fatalf("first concurrent mutation error = %v", err)
|
||||
}
|
||||
if err := <-errs; err != nil {
|
||||
t.Fatalf("second concurrent mutation error = %v", err)
|
||||
}
|
||||
|
||||
locks, _, _, err := loadRemoteLockStore(context.Background(), cfg, fake)
|
||||
if err != nil {
|
||||
t.Fatalf("loadRemoteLockStore() error = %v", err)
|
||||
}
|
||||
if len(locks.Locks) != 2 || locks.Locks[0].Source != "narratio.transcript.final" || locks.Locks[1].Source != "narratio.transcript.final_trimmed" {
|
||||
t.Fatalf("remote locks = %#v, want both concurrent updates", locks.Locks)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadRemoteLockStoreAcceptsExactLimitAndRejectsLimitPlusOne(t *testing.T) {
|
||||
cfg := &config.Config{
|
||||
Pipeline: &config.PipelineConfig{Storage: config.StorageConfig{S3: &config.StorageS3Config{Bucket: "bucket", RootPrefix: "root"}}},
|
||||
Session: &config.SessionConfig{Campaign: "campaign", SessionID: "session"},
|
||||
}
|
||||
key, err := remoteLocksKey(cfg)
|
||||
if err != nil {
|
||||
t.Fatalf("remoteLocksKey() error = %v", err)
|
||||
}
|
||||
encoded, err := config.MarshalPublishLockStore(&config.PublishLockStore{})
|
||||
if err != nil {
|
||||
t.Fatalf("MarshalPublishLockStore() error = %v", err)
|
||||
}
|
||||
exact := append(append([]byte(nil), encoded...), bytes.Repeat([]byte(" "), int(MaxRemoteLockStoreBytes)-len(encoded))...)
|
||||
store := &storage.FakeBackend{}
|
||||
store.SeedObject(storage.FakeObject{Key: key, Data: exact, ETag: "lock-generation"})
|
||||
|
||||
locks, gotKey, generation, err := loadRemoteLockStore(context.Background(), cfg, store)
|
||||
if err != nil {
|
||||
t.Fatalf("loadRemoteLockStore() exact-limit error = %v", err)
|
||||
}
|
||||
if locks == nil || gotKey != key || generation != "lock-generation" {
|
||||
t.Fatalf("loadRemoteLockStore() = (%#v, %q, %q), want decoded locks and opened generation", locks, gotKey, generation)
|
||||
}
|
||||
if len(store.Downloads) != 0 || len(store.Reads) != 1 || store.Reads[0].Key != key {
|
||||
t.Fatalf("lock transfers reads=%#v downloads=%#v, want one direct read", store.Reads, store.Downloads)
|
||||
}
|
||||
|
||||
store.SeedObject(storage.FakeObject{Key: key, Data: append(exact, ' '), ETag: "new-generation"})
|
||||
_, _, _, err = loadRemoteLockStore(context.Background(), cfg, store)
|
||||
if err == nil || !strings.Contains(err.Error(), "remote lock control object") || !strings.Contains(err.Error(), key) || !strings.Contains(err.Error(), fmt.Sprint(MaxRemoteLockStoreBytes)) {
|
||||
t.Fatalf("loadRemoteLockStore() limit-plus-one error = %v, want category, key, and limit", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadRemoteLockStoreRejectsMalformedYAML(t *testing.T) {
|
||||
cfg := &config.Config{
|
||||
Pipeline: &config.PipelineConfig{Storage: config.StorageConfig{S3: &config.StorageS3Config{Bucket: "bucket", RootPrefix: "root"}}},
|
||||
Session: &config.SessionConfig{Campaign: "campaign", SessionID: "session"},
|
||||
}
|
||||
key, err := remoteLocksKey(cfg)
|
||||
if err != nil {
|
||||
t.Fatalf("remoteLocksKey() error = %v", err)
|
||||
}
|
||||
store := &storage.FakeBackend{}
|
||||
store.SeedObject(storage.FakeObject{Key: key, Data: []byte("locks: [\n"), ETag: "lock-generation"})
|
||||
|
||||
if _, _, _, err := loadRemoteLockStore(context.Background(), cfg, store); err == nil {
|
||||
t.Fatal("loadRemoteLockStore() error = nil, want malformed YAML failure")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMutateRemoteLockStoreHonorsCancellation(t *testing.T) {
|
||||
cfg := &config.Config{
|
||||
Pipeline: &config.PipelineConfig{Storage: config.StorageConfig{S3: &config.StorageS3Config{Bucket: "bucket", RootPrefix: "root"}}},
|
||||
Session: &config.SessionConfig{Campaign: "campaign", SessionID: "session"},
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
err := mutateRemoteLockStore(ctx, cfg, &storage.FakeBackend{}, func(*config.PublishLockStore) error { return nil })
|
||||
if !errors.Is(err, context.Canceled) {
|
||||
t.Fatalf("mutateRemoteLockStore() error = %v, want context cancellation", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecuteLocksAddDuplicateRequiresForce(t *testing.T) {
|
||||
workspaceRoot := t.TempDir()
|
||||
pipelinePath, campaignPath, sessionPath := writeValidConfigFiles(t, workspaceRoot)
|
||||
@@ -908,39 +789,6 @@ func TestExecuteLocksMutationRejectsSessionIDMismatch(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecuteLocksDoNotTreatStaticConfigAsAuthority(t *testing.T) {
|
||||
workspaceRoot := t.TempDir()
|
||||
pipelinePath, campaignPath, sessionPath := writeValidConfigFiles(t, workspaceRoot)
|
||||
addStaticPublishLockToPipelineConfig(t, pipelinePath, "narratio.transcript.final_trimmed")
|
||||
fake := &storage.FakeBackend{}
|
||||
var storeInitCalls int
|
||||
restoreAppConfigTestGlobals(t, fake, &storeInitCalls, []string{sessionPath})
|
||||
|
||||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
code := Execute([]string{
|
||||
"session", "locks", "add", "2026-05-03", "narratio.transcript.final_trimmed",
|
||||
"--config", pipelinePath,
|
||||
"--campaign-file", campaignPath,
|
||||
"--session", sessionPath,
|
||||
}, &stdout, &stderr)
|
||||
if code != 0 {
|
||||
t.Fatalf("locks add exit code = %d, want 0; stderr=%q", code, stderr.String())
|
||||
}
|
||||
|
||||
stdout.Reset()
|
||||
stderr.Reset()
|
||||
code = Execute([]string{
|
||||
"session", "locks", "remove", "2026-05-03", "narratio.transcript.final_trimmed",
|
||||
"--config", pipelinePath,
|
||||
"--campaign-file", campaignPath,
|
||||
"--session", sessionPath,
|
||||
}, &stdout, &stderr)
|
||||
if code != 0 {
|
||||
t.Fatalf("locks remove exit code = %d, want 0; stderr=%q", code, stderr.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecuteTopLevelLockAndUnlockAreRemoved(t *testing.T) {
|
||||
tests := []string{"lock", "unlock"}
|
||||
for _, cmd := range tests {
|
||||
@@ -1385,11 +1233,12 @@ func TestExecuteSessionValidateReportsPreviousStateFindingAndReturnsFindingError
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecutePublishLoadsRemoteLocks(t *testing.T) {
|
||||
func TestExecutePublishIgnoresRetiredSharedLockObject(t *testing.T) {
|
||||
workspaceRoot := t.TempDir()
|
||||
pipelinePath, campaignPath, sessionPath := writeValidPublishRunConfigFiles(t, workspaceRoot)
|
||||
fake := &storage.FakeBackend{}
|
||||
lockKey := artifacts.S3SessionLocksKey(artifacts.S3SessionPrefix("dnd", "sample-campaign", "2026-05-03"))
|
||||
sessionPrefix := artifacts.S3SessionPrefix("dnd", "sample-campaign", "2026-05-03")
|
||||
lockKey := sessionPrefix + "locks.yml"
|
||||
fake.SeedObject(storage.FakeObject{Key: lockKey, Data: []byte("locks:\n - source: narratio.transcript.final_trimmed\n reason: remote review\n")})
|
||||
|
||||
workRoot := filepath.Join(workspaceRoot, "work", "sample-campaign", "2026-05-03")
|
||||
@@ -1441,9 +1290,18 @@ func TestExecutePublishLoadsRemoteLocks(t *testing.T) {
|
||||
if storeInitCalls != 1 {
|
||||
t.Fatalf("object store initialization calls = %d, want 1", storeInitCalls)
|
||||
}
|
||||
publishedKey := artifacts.S3PublishedOutputKey(artifacts.S3SessionPrefix("dnd", "sample-campaign", "2026-05-03"), "transcripts/final.trimmed.json")
|
||||
if _, ok := fake.Objects[publishedKey]; ok {
|
||||
t.Fatalf("locked published key %q was uploaded", publishedKey)
|
||||
current, err := artifacts.LoadCurrentState(context.Background(), fake, sessionPrefix, artifacts.CurrentStateValidation{ValidateRunID: true})
|
||||
if err != nil {
|
||||
t.Fatalf("LoadCurrentState() error = %v", err)
|
||||
}
|
||||
found := false
|
||||
for _, declaration := range current.Commit.Artifacts {
|
||||
if declaration.Type == artifacts.RemoteArtifactTypePublishedOutput && declaration.Source == "narratio.transcript.final_trimmed" {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Fatal("retired shared lock object affected publication")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1607,23 +1465,3 @@ func writeValidPublishRunConfigFiles(t *testing.T, workspaceRoot string) (string
|
||||
}
|
||||
return pipelinePath, campaignPath, sessionPath
|
||||
}
|
||||
|
||||
func addStaticPublishLockToPipelineConfig(t *testing.T, pipelinePath, source string) {
|
||||
t.Helper()
|
||||
data, err := os.ReadFile(pipelinePath)
|
||||
if err != nil {
|
||||
t.Fatalf("read pipeline: %v", err)
|
||||
}
|
||||
updated := strings.Replace(
|
||||
string(data),
|
||||
"publish:\n enabled: true\n upload_run: false\n",
|
||||
"publish:\n enabled: true\n upload_run: false\n locks:\n - source: "+source+"\n reason: static review\n",
|
||||
1,
|
||||
)
|
||||
if updated == string(data) {
|
||||
t.Fatalf("publish section not found in pipeline config")
|
||||
}
|
||||
if err := os.WriteFile(pipelinePath, []byte(updated), 0o644); err != nil {
|
||||
t.Fatalf("write pipeline: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user