migrations_test.go raw
1 package neo4j
2
3 import (
4 "context"
5 "testing"
6 )
7
8 // TestMigrationV2_CleanupBinaryEncodedValues tests that migration v2 properly
9 // cleans up binary-encoded pubkeys and event IDs
10 func TestMigrationV2_CleanupBinaryEncodedValues(t *testing.T) {
11 if testDB == nil {
12 t.Skip("Neo4j not available")
13 }
14
15 // Clean up before test
16 cleanTestDatabase()
17
18 ctx := context.Background()
19
20 // Create some valid NostrUser nodes (should NOT be deleted)
21 validPubkeys := []string{
22 "0000000000000000000000000000000000000000000000000000000000000001",
23 "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789",
24 }
25 for _, pk := range validPubkeys {
26 setupInvalidNostrUser(t, pk) // Using setupInvalidNostrUser to create directly
27 }
28
29 // Create some invalid NostrUser nodes (should be deleted)
30 invalidPubkeys := []string{
31 "binary\x00garbage\x00data", // Binary garbage
32 "ABCDEF", // Too short
33 "GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG", // Non-hex chars
34 string(append(make([]byte, 32), 0)), // 33-byte binary format
35 }
36 for _, pk := range invalidPubkeys {
37 setupInvalidNostrUser(t, pk)
38 }
39
40 // Verify invalid nodes exist before migration
41 invalidCountBefore := countInvalidNostrUsers(t)
42 if invalidCountBefore != 4 {
43 t.Errorf("Expected 4 invalid NostrUsers before migration, got %d", invalidCountBefore)
44 }
45
46 totalBefore := countNodes(t, "NostrUser")
47 if totalBefore != 6 {
48 t.Errorf("Expected 6 total NostrUsers before migration, got %d", totalBefore)
49 }
50
51 // Run the migration
52 err := migrateBinaryToHex(ctx, testDB)
53 if err != nil {
54 t.Fatalf("Migration failed: %v", err)
55 }
56
57 // Verify invalid nodes were deleted
58 invalidCountAfter := countInvalidNostrUsers(t)
59 if invalidCountAfter != 0 {
60 t.Errorf("Expected 0 invalid NostrUsers after migration, got %d", invalidCountAfter)
61 }
62
63 // Verify valid nodes were NOT deleted
64 totalAfter := countNodes(t, "NostrUser")
65 if totalAfter != 2 {
66 t.Errorf("Expected 2 valid NostrUsers after migration, got %d", totalAfter)
67 }
68 }
69
70 // TestMigrationV2_CleanupInvalidEvents tests that migration v2 properly
71 // cleans up Event nodes with invalid pubkeys or IDs
72 func TestMigrationV2_CleanupInvalidEvents(t *testing.T) {
73 if testDB == nil {
74 t.Skip("Neo4j not available")
75 }
76
77 // Clean up before test
78 cleanTestDatabase()
79
80 ctx := context.Background()
81
82 // Create valid events
83 validEventID := "1111111111111111111111111111111111111111111111111111111111111111"
84 validPubkey := "0000000000000000000000000000000000000000000000000000000000000001"
85 setupTestEvent(t, validEventID, validPubkey, 1, "[]")
86
87 // Create invalid events directly
88 setupInvalidEvent(t, "invalid_id", validPubkey) // Invalid ID
89 setupInvalidEvent(t, validEventID+"2", "invalid_pubkey") // Invalid pubkey (different ID to avoid duplicate)
90 setupInvalidEvent(t, "TOOSHORT", "binary\x00garbage") // Both invalid
91
92 // Count events before migration
93 eventsBefore := countNodes(t, "Event")
94 if eventsBefore != 4 {
95 t.Errorf("Expected 4 Events before migration, got %d", eventsBefore)
96 }
97
98 // Run the migration
99 err := migrateBinaryToHex(ctx, testDB)
100 if err != nil {
101 t.Fatalf("Migration failed: %v", err)
102 }
103
104 // Verify only valid event remains
105 eventsAfter := countNodes(t, "Event")
106 if eventsAfter != 1 {
107 t.Errorf("Expected 1 valid Event after migration, got %d", eventsAfter)
108 }
109 }
110
111 // TestMigrationV2_CleanupInvalidTags tests that migration v2 properly
112 // cleans up Tag nodes (e/p type) with invalid values
113 func TestMigrationV2_CleanupInvalidTags(t *testing.T) {
114 if testDB == nil {
115 t.Skip("Neo4j not available")
116 }
117
118 // Clean up before test
119 cleanTestDatabase()
120
121 ctx := context.Background()
122
123 // Create valid tags
124 validHex := "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789"
125 setupInvalidTag(t, "e", validHex) // Valid e-tag
126 setupInvalidTag(t, "p", validHex) // Valid p-tag
127 setupInvalidTag(t, "t", "topic") // Non e/p tag (should not be affected)
128
129 // Create invalid e/p tags
130 setupInvalidTag(t, "e", "binary\x00garbage") // Invalid e-tag
131 setupInvalidTag(t, "p", "TOOSHORT") // Invalid p-tag (too short)
132 setupInvalidTag(t, "e", string(append(make([]byte, 32), 0))) // Binary encoded
133
134 // Count tags before migration
135 tagsBefore := countNodes(t, "Tag")
136 if tagsBefore != 6 {
137 t.Errorf("Expected 6 Tags before migration, got %d", tagsBefore)
138 }
139
140 invalidBefore := countInvalidTags(t)
141 if invalidBefore != 3 {
142 t.Errorf("Expected 3 invalid e/p Tags before migration, got %d", invalidBefore)
143 }
144
145 // Run the migration
146 err := migrateBinaryToHex(ctx, testDB)
147 if err != nil {
148 t.Fatalf("Migration failed: %v", err)
149 }
150
151 // Verify invalid tags were deleted
152 invalidAfter := countInvalidTags(t)
153 if invalidAfter != 0 {
154 t.Errorf("Expected 0 invalid e/p Tags after migration, got %d", invalidAfter)
155 }
156
157 // Verify valid tags remain (2 e/p valid + 1 t-tag)
158 tagsAfter := countNodes(t, "Tag")
159 if tagsAfter != 3 {
160 t.Errorf("Expected 3 Tags after migration, got %d", tagsAfter)
161 }
162 }
163
164 // TestMigrationV2_Idempotent tests that migration v2 can be run multiple times safely
165 func TestMigrationV2_Idempotent(t *testing.T) {
166 if testDB == nil {
167 t.Skip("Neo4j not available")
168 }
169
170 // Clean up before test
171 cleanTestDatabase()
172
173 ctx := context.Background()
174
175 // Create only valid data
176 validPubkey := "0000000000000000000000000000000000000000000000000000000000000001"
177 validEventID := "1111111111111111111111111111111111111111111111111111111111111111"
178 setupTestEvent(t, validEventID, validPubkey, 1, "[]")
179
180 countBefore := countNodes(t, "Event")
181
182 // Run migration first time
183 err := migrateBinaryToHex(ctx, testDB)
184 if err != nil {
185 t.Fatalf("First migration run failed: %v", err)
186 }
187
188 countAfterFirst := countNodes(t, "Event")
189 if countAfterFirst != countBefore {
190 t.Errorf("First migration changed valid event count: before=%d, after=%d", countBefore, countAfterFirst)
191 }
192
193 // Run migration second time
194 err = migrateBinaryToHex(ctx, testDB)
195 if err != nil {
196 t.Fatalf("Second migration run failed: %v", err)
197 }
198
199 countAfterSecond := countNodes(t, "Event")
200 if countAfterSecond != countBefore {
201 t.Errorf("Second migration changed valid event count: before=%d, after=%d", countBefore, countAfterSecond)
202 }
203 }
204
205 // TestMigrationV2_NoDataDoesNotFail tests that migration v2 succeeds with empty database
206 func TestMigrationV2_NoDataDoesNotFail(t *testing.T) {
207 if testDB == nil {
208 t.Skip("Neo4j not available")
209 }
210
211 // Clean up completely
212 cleanTestDatabase()
213
214 ctx := context.Background()
215
216 // Run migration on empty database - should not fail
217 err := migrateBinaryToHex(ctx, testDB)
218 if err != nil {
219 t.Fatalf("Migration on empty database failed: %v", err)
220 }
221 }
222
223 // TestMigrationMarking tests that migrations are properly tracked
224 func TestMigrationMarking(t *testing.T) {
225 if testDB == nil {
226 t.Skip("Neo4j not available")
227 }
228
229 // Clean up before test
230 cleanTestDatabase()
231
232 ctx := context.Background()
233
234 // Verify migration v2 has not been applied
235 if testDB.migrationApplied(ctx, "v2") {
236 t.Error("Migration v2 should not be applied before test")
237 }
238
239 // Mark migration as complete
240 err := testDB.markMigrationComplete(ctx, "v2", "Test migration")
241 if err != nil {
242 t.Fatalf("Failed to mark migration complete: %v", err)
243 }
244
245 // Verify migration is now marked as applied
246 if !testDB.migrationApplied(ctx, "v2") {
247 t.Error("Migration v2 should be applied after marking")
248 }
249
250 // Clean up
251 cleanTestDatabase()
252 }
253
254 // TestMigrationV1_AuthorToNostrUserMerge tests the author migration
255 func TestMigrationV1_AuthorToNostrUserMerge(t *testing.T) {
256 if testDB == nil {
257 t.Skip("Neo4j not available")
258 }
259
260 // Clean up before test
261 cleanTestDatabase()
262
263 ctx := context.Background()
264
265 // Create some Author nodes (legacy format)
266 authorPubkeys := []string{
267 "0000000000000000000000000000000000000000000000000000000000000001",
268 "0000000000000000000000000000000000000000000000000000000000000002",
269 }
270
271 for _, pk := range authorPubkeys {
272 cypher := `CREATE (a:Author {pubkey: $pubkey})`
273 _, err := testDB.ExecuteWrite(ctx, cypher, map[string]any{"pubkey": pk})
274 if err != nil {
275 t.Fatalf("Failed to create Author node: %v", err)
276 }
277 }
278
279 // Verify Author nodes exist
280 authorCount := countNodes(t, "Author")
281 if authorCount != 2 {
282 t.Errorf("Expected 2 Author nodes, got %d", authorCount)
283 }
284
285 // Run migration
286 err := migrateAuthorToNostrUser(ctx, testDB)
287 if err != nil {
288 t.Fatalf("Migration failed: %v", err)
289 }
290
291 // Verify NostrUser nodes were created
292 nostrUserCount := countNodes(t, "NostrUser")
293 if nostrUserCount != 2 {
294 t.Errorf("Expected 2 NostrUser nodes after migration, got %d", nostrUserCount)
295 }
296
297 // Verify Author nodes were deleted (they should have no relationships after migration)
298 authorCountAfter := countNodes(t, "Author")
299 if authorCountAfter != 0 {
300 t.Errorf("Expected 0 Author nodes after migration, got %d", authorCountAfter)
301 }
302 }
303