Skip to content
quarry Artifact [07cafb420a]
Login

Artifact [07cafb420a]

Artifact 07cafb420aabca339f9080b2199a1c3fee2ce0bb3b39bd57ae413b28f2832fa5:


package api

import (
	"context"
	"errors"
	"strings"
	"testing"

	"craftdesign.group/quarry/internal/ops"
)

// An attachment the store holds and the index has not folded yet is
// neither invisible nor unowned.
//
// Measured by jva from a Developer seat: removing a file seconds after
// uploading it answered "nothing named janown.txt is attached", about a
// file the store was holding. Two things were wrong in that window and
// only one of them showed. The refusal said the attachment did not
// exist, when the truth was that it was not indexed yet and a retry
// would work. And mayRemove, finding no row, fell through and decided
// NOTHING — the caller was not allowed and not refused, merely
// unexamined. Nothing was destroyed, because remove() refused straight
// after on the same missing row, so the gap never showed as a breach;
// it was a gate that happened to be standing next to a closed door.
//
// Both halves now ask the store when the index comes up empty.
func TestAnUnindexedAttachmentIsStillOwned(t *testing.T) {
	ix, z, event := attachStore(t)
	ctx := context.Background()
	// Written to the STORE and deliberately never folded: this is the
	// window, reproduced exactly rather than waited for.
	if _, _, err := z.AttachAdd(testToken, ctx, event, "lou.txt", "text/plain", "lou", "", []byte("lou's file\n")); err != nil {
		t.Fatal(err)
	}
	d := Deps{Version: "v", Repos: NewRepos(map[string]*Repo{"test": {Index: ix, Zit: z}})}
	r := ops.NewRegistry()
	if err := RegisterAll(r, d); err != nil {
		t.Fatal(err)
	}

	// A non-owner is REFUSED rather than falling through the gate.
	_, err := r.Invoke(as("dan", "bB"), "attachment.remove",
		attachmentRemoveIn{Repo: "test", Target: event, Name: "lou.txt"})
	if !errors.Is(err, ops.ErrForbidden) {
		t.Errorf("FAIL: an unindexed attachment was not gated: %v", err)
	} else if !strings.Contains(err.Error(), "lou") {
		t.Errorf("FAIL: the refusal does not name the uploader: %v", err)
	}

	// The OWNER gets past the gate and meets the honest answer: it is
	// here, it is not indexed, try again — not "it does not exist".
	_, err = r.Invoke(as("lou", "bB"), "attachment.remove",
		attachmentRemoveIn{Repo: "test", Target: event, Name: "lou.txt"})
	if !errors.Is(err, ops.ErrUnavailable) {
		t.Errorf("FAIL: the owner did not get the not-indexed-yet answer: %v", err)
	} else if strings.Contains(err.Error(), "nothing named") {
		t.Errorf("FAIL: a file the store holds was reported absent: %v", err)
	}

	// A file that really is absent still answers not-found, from both
	// sources agreeing rather than from one of them being unread.
	_, err = r.Invoke(as("lou", "bB"), "attachment.remove",
		attachmentRemoveIn{Repo: "test", Target: event, Name: "never-existed.txt"})
	if !errors.Is(err, ops.ErrNotFound) {
		t.Errorf("FAIL: an absent attachment did not answer not-found: %v", err)
	}
}