Approve/commit now write FAV staging in the domain transaction, recover-apply materializes scratch collections, and unpublished or GET paths cannot mutate the registry. Co-authored-by: Cursor <cursoragent@cursor.com>
108 lines
3.6 KiB
Go
108 lines
3.6 KiB
Go
package artifactvault
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestEvaluateEligibility_Allowlist(t *testing.T) {
|
|
ok := Facts{
|
|
ArtifactType: TypeAgentCommit, CandidateStatus: "committed", OutputSnapshotID: 9,
|
|
ChangeKind: "agent_commit", ChangeToSnapshotID: 9, Classification: ClassificationOK, Payload: []byte("doc"),
|
|
}
|
|
if err := EvaluateEligibility(ok); err != nil {
|
|
t.Fatalf("committed should pass: %v", err)
|
|
}
|
|
for _, status := range []string{"ready", "needs_confirmation", "rejected", "stale", "quality_failed"} {
|
|
bad := ok
|
|
bad.CandidateStatus = status
|
|
if err := EvaluateEligibility(bad); err == nil {
|
|
t.Fatalf("status %s must reject", status)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestEvaluateEligibility_ApprovedRequiresReviewSnapshot(t *testing.T) {
|
|
now := time.Now().UTC()
|
|
f := Facts{
|
|
ArtifactType: TypeApprovedDocument, DocState: "approved", ApprovedAt: &now,
|
|
Classification: ClassificationOK, Payload: []byte("approved"),
|
|
}
|
|
if err := EvaluateEligibility(f); err == nil {
|
|
t.Fatal("approved without review_snapshot_id must reject")
|
|
}
|
|
f.ReviewSnapshotID = 3
|
|
if err := EvaluateEligibility(f); err != nil {
|
|
t.Fatalf("approved with snapshot should pass: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestEvaluateEligibility_TaskWithoutCompletedTurn(t *testing.T) {
|
|
f := Facts{
|
|
ArtifactType: TypeAssistantReply, ChatTurnStatus: "", AssistantMessageID: 8,
|
|
ExportPolicy: "full", Classification: ClassificationOK, Payload: []byte("hello world from assistant"),
|
|
}
|
|
if err := EvaluateEligibility(f); err == nil {
|
|
t.Fatal("missing completed turn must reject")
|
|
}
|
|
if err := EvaluateEligibility(Facts{
|
|
ArtifactType: TypeAssistantReply, ExplicitPin: true, ChatTurnStatus: "pending",
|
|
Classification: ClassificationOK, Payload: []byte("hello world from assistant"),
|
|
}); err != ErrExplicitBypass {
|
|
t.Fatalf("pin before completed: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestEvaluateEligibility_SensitiveRejectedBeforeStaging(t *testing.T) {
|
|
now := time.Now().UTC()
|
|
f := Facts{
|
|
ArtifactType: TypeAssistantReply, ChatTurnStatus: "completed", CompletedAt: &now,
|
|
AssistantMessageID: 1, ExportPolicy: "full", Payload: []byte("api_key=sk-secret"),
|
|
}
|
|
if err := EvaluateEligibility(f); err != ErrSensitiveRejected {
|
|
t.Fatalf("secret payload: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestEvaluateEligibility_CopyOnlyRequiresExplicitPinAfterCompleted(t *testing.T) {
|
|
now := time.Now().UTC()
|
|
base := Facts{
|
|
ArtifactType: TypeAssistantReply, ChatTurnStatus: "completed", CompletedAt: &now,
|
|
AssistantMessageID: 9, ExportPolicy: "copy_only", Classification: ClassificationOK,
|
|
Payload: []byte("ok, noted."),
|
|
}
|
|
if err := EvaluateEligibility(base); err == nil {
|
|
t.Fatal("copy_only without pin must reject")
|
|
}
|
|
base.ExplicitPin = true
|
|
if err := EvaluateEligibility(base); err != ErrExplicitBypass {
|
|
t.Fatalf("pin without PinAfterCompleted: %v", err)
|
|
}
|
|
base.PinAfterCompleted = true
|
|
if err := EvaluateEligibility(base); err != nil {
|
|
t.Fatalf("copy_only + pin after completed should pass: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestEvaluateEligibility_UnknownTypeRejected(t *testing.T) {
|
|
if err := EvaluateEligibility(Facts{ArtifactType: "workspace_scan", Classification: ClassificationOK, Payload: []byte("x")}); err != ErrUnknownArtifactType {
|
|
t.Fatalf("got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestIdempotencyKeyStable(t *testing.T) {
|
|
req := PublishRequest{
|
|
ArtifactType: TypeAgentCommit, Environment: "dev", ProjectExternalKey: "p1",
|
|
LogicalResourceID: "doc_a", SourceEventKind: "agent_commit", SourceEventID: "ev1",
|
|
}
|
|
a := IdempotencyKey(req, "abc")
|
|
b := IdempotencyKey(req, "abc")
|
|
if a != b || a == "" {
|
|
t.Fatalf("key unstable %q %q", a, b)
|
|
}
|
|
req.SourceEventID = "ev2"
|
|
if IdempotencyKey(req, "abc") == a {
|
|
t.Fatal("new event must new key")
|
|
}
|
|
}
|