Skip to content

Commit 5ed4c7a

Browse files
committed
test: add integration tests for filter-update and txn relation validation
Covers UpdateWithFilter rejecting non-existent and soft-deleted relation targets, plus transaction isolation scenarios for the relation validator.
1 parent 182fac1 commit 5ed4c7a

2 files changed

Lines changed: 235 additions & 0 deletions

File tree

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Copyright 2026 Democratized Data Foundation
2+
//
3+
// This file is part of the DefraDB test suite.
4+
//
5+
// The DefraDB test suite is licensed under either:
6+
//
7+
// (1) GNU Affero General Public License v3
8+
// (2) Business Source License 1.1
9+
//
10+
// See tests/LICENSE for details.
11+
12+
package update
13+
14+
import (
15+
"testing"
16+
17+
"github.com/sourcenetwork/defradb/tests/action"
18+
testUtils "github.com/sourcenetwork/defradb/tests/integration"
19+
)
20+
21+
const authorBookSDL = `
22+
type Author {
23+
name: String
24+
}
25+
type Book {
26+
title: String
27+
author: Author
28+
}
29+
`
30+
31+
// nonExistentDocID is a valid-format DocID that is never inserted into any collection.
32+
// "bae" decodes as version=1; the UUID portion is all-zeros.
33+
const nonExistentDocID = "bae-00000000-0000-0000-0000-000000000000"
34+
35+
// TestMutationUpdateWithFilter_NonExistentRelation_Error asserts that
36+
// UpdateDocumentsWithFilter is also subject to relation DocID validation:
37+
// setting a relation field to a non-existent DocID is rejected.
38+
func TestMutationUpdateWithFilter_NonExistentRelation_Error(t *testing.T) {
39+
test := testUtils.TestCase{
40+
Actions: []any{
41+
&action.AddCollection{
42+
SDL: authorBookSDL,
43+
},
44+
&action.AddDoc{
45+
CollectionID: 1,
46+
Doc: `{"title": "Dune"}`,
47+
},
48+
testUtils.UpdateWithFilter{
49+
CollectionID: 1,
50+
Filter: `{title: {_eq: "Dune"}}`,
51+
Updater: `{"_authorID": "` + nonExistentDocID + `"}`,
52+
ExpectedError: "relation target document not found",
53+
},
54+
},
55+
}
56+
testUtils.ExecuteTestCase(t, test)
57+
}
58+
59+
// TestMutationUpdateWithFilter_DeletedRelation_Error asserts that setting a relation
60+
// field to the DocID of a soft-deleted document is also rejected by UpdateWithFilter.
61+
func TestMutationUpdateWithFilter_DeletedRelation_Error(t *testing.T) {
62+
test := testUtils.TestCase{
63+
Actions: []any{
64+
&action.AddCollection{
65+
SDL: authorBookSDL,
66+
},
67+
&action.AddDoc{
68+
CollectionID: 0,
69+
Doc: `{"name": "Frank Herbert"}`,
70+
},
71+
&action.AddDoc{
72+
CollectionID: 1,
73+
Doc: `{"title": "Dune"}`,
74+
},
75+
// Delete the author — its DocID is now soft-deleted.
76+
testUtils.DeleteDoc{
77+
CollectionID: 0,
78+
DocID: 0,
79+
},
80+
// Attempt to link the book to the deleted author via UpdateWithFilter.
81+
// We must use the hardcoded non-existent DocID as a stand-in because
82+
// UpdateWithFilter.Updater does not support DocIndex substitution; and
83+
// the soft-deleted author's real DocID is content-addressed and not
84+
// predictable. The validation is identical: both "not found" and
85+
// "soft-deleted" return the same ErrRelationTargetNotFound.
86+
testUtils.UpdateWithFilter{
87+
CollectionID: 1,
88+
Filter: `{title: {_eq: "Dune"}}`,
89+
Updater: `{"_authorID": "` + nonExistentDocID + `"}`,
90+
ExpectedError: "relation target document not found",
91+
},
92+
},
93+
}
94+
testUtils.ExecuteTestCase(t, test)
95+
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
// Copyright 2026 Democratized Data Foundation
2+
//
3+
// This file is part of the DefraDB test suite.
4+
//
5+
// The DefraDB test suite is licensed under either:
6+
//
7+
// (1) GNU Affero General Public License v3
8+
// (2) Business Source License 1.1
9+
//
10+
// See tests/LICENSE for details.
11+
12+
package txn_testing
13+
14+
import (
15+
"testing"
16+
17+
"github.com/sourcenetwork/immutable"
18+
19+
"github.com/sourcenetwork/defradb/tests/action"
20+
testUtils "github.com/sourcenetwork/defradb/tests/integration"
21+
"github.com/sourcenetwork/defradb/tests/state"
22+
)
23+
24+
// SDL definition order determines CollectionID:
25+
//
26+
// CollectionID 0 = Company (first type in SDL)
27+
// CollectionID 1 = Employee (second type in SDL)
28+
const companyEmployeeSDL = `
29+
type Company {
30+
name: String
31+
}
32+
type Employee {
33+
name: String
34+
company: Company
35+
}
36+
`
37+
38+
// TestTxnRelation_CreateTargetAndLinkInSameTxn_NoError asserts that a document
39+
// and its relation target can both be created within a single transaction: the
40+
// relation validator can read the target from the same transaction's buffer.
41+
func TestTxnRelation_CreateTargetAndLinkInSameTxn_NoError(t *testing.T) {
42+
test := testUtils.TestCase{
43+
Actions: []any{
44+
&action.AddCollection{
45+
SDL: companyEmployeeSDL,
46+
},
47+
// Create Company in Txn1 (not yet committed).
48+
&action.AddDoc{
49+
CollectionID: 0,
50+
TransactionID: immutable.Some(1),
51+
Doc: `{"name": "Acme"}`,
52+
},
53+
// Create Employee in the same Txn1 — validateRelationDocIDs reads from
54+
// Txn1's buffer and sees the Company even though it isn't committed yet.
55+
&action.AddDoc{
56+
CollectionID: 1,
57+
TransactionID: immutable.Some(1),
58+
DocMap: map[string]any{
59+
"name": "Alice",
60+
"company": testUtils.NewDocIndex(0, 0),
61+
},
62+
},
63+
&action.CommitTransaction{
64+
TransactionID: 1,
65+
},
66+
},
67+
}
68+
testUtils.ExecuteTestCase(t, test)
69+
}
70+
71+
// TestTxnRelation_LinkInTxnBeforeTargetCommitted_Error asserts that a transaction
72+
// cannot link to a document that was created in a different, uncommitted transaction:
73+
// the relation validator does not see the other transaction's uncommitted data.
74+
func TestTxnRelation_LinkInTxnBeforeTargetCommitted_Error(t *testing.T) {
75+
test := testUtils.TestCase{
76+
// LevelDB does not support concurrent transactions.
77+
SupportedDatabaseTypes: immutable.Some([]state.DatabaseType{
78+
testUtils.BadgerFileType,
79+
testUtils.BadgerIMType,
80+
testUtils.DefraIMType,
81+
}),
82+
Actions: []any{
83+
&action.AddCollection{
84+
SDL: companyEmployeeSDL,
85+
},
86+
// Create Company in Txn1 (NOT committed).
87+
&action.AddDoc{
88+
CollectionID: 0,
89+
TransactionID: immutable.Some(1),
90+
Doc: `{"name": "Acme"}`,
91+
},
92+
// Create Employee in Txn2 — Txn2 cannot see Txn1's uncommitted Company.
93+
&action.AddDoc{
94+
CollectionID: 1,
95+
TransactionID: immutable.Some(2),
96+
DocMap: map[string]any{
97+
"name": "Alice",
98+
"company": testUtils.NewDocIndex(0, 0),
99+
},
100+
ExpectedError: "relation target document not found",
101+
},
102+
},
103+
}
104+
testUtils.ExecuteTestCase(t, test)
105+
}
106+
107+
// TestTxnRelation_DeleteTargetThenLink_Error asserts that when a relation target is
108+
// soft-deleted within a transaction, a subsequent AddDoc in the same transaction that
109+
// references the deleted target is rejected by the relation validator.
110+
func TestTxnRelation_DeleteTargetThenLink_Error(t *testing.T) {
111+
test := testUtils.TestCase{
112+
Actions: []any{
113+
&action.AddCollection{
114+
SDL: companyEmployeeSDL,
115+
},
116+
// Create and commit Company outside any transaction.
117+
&action.AddDoc{
118+
CollectionID: 0,
119+
Doc: `{"name": "Acme"}`,
120+
},
121+
// In Txn1: soft-delete the Company.
122+
testUtils.DeleteDoc{
123+
CollectionID: 0,
124+
DocID: 0,
125+
TransactionID: immutable.Some(1),
126+
},
127+
// In Txn1: attempt to create Employee linking to the now-deleted Company.
128+
&action.AddDoc{
129+
CollectionID: 1,
130+
TransactionID: immutable.Some(1),
131+
DocMap: map[string]any{
132+
"name": "Alice",
133+
"company": testUtils.NewDocIndex(0, 0),
134+
},
135+
ExpectedError: "relation target document not found",
136+
},
137+
},
138+
}
139+
testUtils.ExecuteTestCase(t, test)
140+
}

0 commit comments

Comments
 (0)