-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathobservation_compaction_test.go
More file actions
369 lines (316 loc) · 10.3 KB
/
Copy pathobservation_compaction_test.go
File metadata and controls
369 lines (316 loc) · 10.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
package nara
import (
"testing"
"time"
"github.com/eljojo/nara/types"
)
// Test that we can add up to 20 observation events per observer→subject pair
func TestObservationCompaction_UnderLimit(t *testing.T) {
ledger := NewSyncLedger(1000)
observer := types.NaraName("nara-a")
subject := types.NaraName("nara-b")
// Add 20 events for the same observer→subject pair
for i := 0; i < 20; i++ {
event := NewRestartObservationEvent(observer, subject, time.Now().Unix(), int64(i))
added := ledger.AddEvent(event)
if !added {
t.Errorf("Event %d should be added (under limit)", i)
}
}
// Verify all 20 events are present
events := ledger.GetObservationEventsAbout(subject)
count := 0
for _, e := range events {
if e.Observation.Observer == observer && e.Observation.Subject == subject {
count++
}
}
if count != 20 {
t.Errorf("Expected 20 events for %s→%s, got %d", observer, subject, count)
}
}
// Test that adding the 21st event evicts the oldest (when checkpoint exists)
// Note: Without a checkpoint, restart events are preserved to avoid losing history
func TestObservationCompaction_OverLimit(t *testing.T) {
ledger := NewSyncLedger(1000)
observer := types.NaraName("nara-a")
subject := types.NaraName("nara-b")
// First add a checkpoint so compaction of restart events is allowed
checkpoint := NewTestCheckpointEvent(subject, time.Now().Unix()-3600, time.Now().Unix()-86400, 0, 0)
ledger.AddEvent(checkpoint)
// Add 21 events with distinct restart numbers
for i := 0; i < 21; i++ {
event := NewRestartObservationEvent(observer, subject, time.Now().Unix(), int64(i))
time.Sleep(1 * time.Millisecond) // Ensure different timestamps
ledger.AddEvent(event)
}
// Should have exactly 20 events
events := ledger.GetObservationEventsAbout(subject)
count := 0
for _, e := range events {
if e.Observation.Observer == observer && e.Observation.Subject == subject {
count++
}
}
if count != 20 {
t.Errorf("Expected exactly 20 events after compaction, got %d", count)
}
// The oldest event (restart_num=0) should be evicted
hasOldest := false
hasNewest := false
for _, e := range events {
if e.Observation.Observer == observer && e.Observation.Subject == subject {
if e.Observation.RestartNum == 0 {
hasOldest = true
}
if e.Observation.RestartNum == 20 {
hasNewest = true
}
}
}
if hasOldest {
t.Error("Oldest event (restart_num=0) should have been evicted")
}
if !hasNewest {
t.Error("Newest event (restart_num=20) should be present")
}
}
// Test that multiple observer→subject pairs don't interfere with each other
func TestObservationCompaction_MultiplePairs(t *testing.T) {
ledger := NewSyncLedger(1000)
// Add 20 events for alice→bob
for i := 0; i < 20; i++ {
event := NewRestartObservationEvent(types.NaraName("alice"), types.NaraName("bob"), time.Now().Unix(), int64(i))
ledger.AddEvent(event)
}
// Add 20 events for alice→charlie
for i := 0; i < 20; i++ {
event := NewRestartObservationEvent(types.NaraName("alice"), types.NaraName("charlie"), time.Now().Unix(), int64(i))
ledger.AddEvent(event)
}
// Add 20 events for dave→bob
for i := 0; i < 20; i++ {
event := NewRestartObservationEvent(types.NaraName("dave"), types.NaraName("bob"), time.Now().Unix(), int64(i))
ledger.AddEvent(event)
}
// Each pair should have 20 events
bobEvents := ledger.GetObservationEventsAbout(types.NaraName("bob"))
aliceToBobCount := 0
daveToBobCount := 0
for _, e := range bobEvents {
if e.Observation.Observer == types.NaraName("alice") {
aliceToBobCount++
}
if e.Observation.Observer == types.NaraName("dave") {
daveToBobCount++
}
}
if aliceToBobCount != 20 {
t.Errorf("Expected 20 events for alice→bob, got %d", aliceToBobCount)
}
if daveToBobCount != 20 {
t.Errorf("Expected 20 events for dave→bob, got %d", daveToBobCount)
}
charlieEvents := ledger.GetObservationEventsAbout(types.NaraName("charlie"))
aliceToCharlieCount := 0
for _, e := range charlieEvents {
if e.Observation.Observer == types.NaraName("alice") {
aliceToCharlieCount++
}
}
if aliceToCharlieCount != 20 {
t.Errorf("Expected 20 events for alice→charlie, got %d", aliceToCharlieCount)
}
}
// Test compaction with different observation types
func TestObservationCompaction_MixedTypes(t *testing.T) {
ledger := NewSyncLedger(1000)
observer := types.NaraName("nara-a")
subject := types.NaraName("nara-b")
// Add 10 restart events
for i := 0; i < 10; i++ {
event := NewRestartObservationEvent(observer, subject, time.Now().Unix(), int64(i))
ledger.AddEvent(event)
}
// Add 10 status-change events
for i := 0; i < 10; i++ {
var state string
if i%2 == 0 {
state = "ONLINE"
} else {
state = "OFFLINE"
}
event := NewStatusChangeObservationEvent(observer, subject, state)
ledger.AddEvent(event)
}
// Should have exactly 20 events total
events := ledger.GetObservationEventsAbout(subject)
count := 0
for _, e := range events {
if e.Observation.Observer == observer && e.Observation.Subject == subject {
count++
}
}
if count != 20 {
t.Errorf("Expected 20 events total (mixed types), got %d", count)
}
// Add 5 more restart events
for i := 10; i < 15; i++ {
event := NewRestartObservationEvent(observer, subject, time.Now().Unix(), int64(i))
ledger.AddEvent(event)
}
// Should still have exactly 20 events (compaction should evict oldest 5)
events = ledger.GetObservationEventsAbout(subject)
count = 0
for _, e := range events {
if e.Observation.Observer == observer && e.Observation.Subject == subject {
count++
}
}
if count != 20 {
t.Errorf("Expected 20 events after adding more (should compact), got %d", count)
}
}
// Test compaction behavior at exact limit
func TestObservationCompaction_ExactLimit(t *testing.T) {
ledger := NewSyncLedger(1000)
observer := types.NaraName("nara-a")
subject := types.NaraName("nara-b")
// Add exactly 20 events
for i := 0; i < 20; i++ {
event := NewRestartObservationEvent(observer, subject, time.Now().Unix(), int64(i))
ledger.AddEvent(event)
}
events := ledger.GetObservationEventsAbout(subject)
count := 0
for _, e := range events {
if e.Observation.Observer == observer && e.Observation.Subject == subject {
count++
}
}
if count != 20 {
t.Errorf("Expected exactly 20 events at limit, got %d", count)
}
// Verify no compaction happened yet (all original events should be present)
for i := 0; i < 20; i++ {
found := false
for _, e := range events {
if e.Observation.Observer == observer &&
e.Observation.Subject == subject &&
e.Observation.RestartNum == int64(i) {
found = true
break
}
}
if !found {
t.Errorf("Expected to find restart_num=%d, but it's missing", i)
}
}
}
// Test heavy compaction (adding 100 events, keeping only 20 when checkpoint exists)
func TestObservationCompaction_HeavyLoad(t *testing.T) {
ledger := NewSyncLedger(2000)
observer := types.NaraName("nara-a")
subject := types.NaraName("nara-b")
// First add a checkpoint so compaction of restart events is allowed
checkpoint := NewTestCheckpointEvent(subject, time.Now().Unix()-3600, time.Now().Unix()-86400, 0, 0)
ledger.AddEvent(checkpoint)
// Add 100 events
for i := 0; i < 100; i++ {
event := NewRestartObservationEvent(observer, subject, time.Now().Unix(), int64(i))
ledger.AddEvent(event)
}
// Should have exactly 20 events (most recent)
events := ledger.GetObservationEventsAbout(subject)
count := 0
for _, e := range events {
if e.Observation.Observer == observer && e.Observation.Subject == subject {
count++
}
}
if count != 20 {
t.Errorf("Expected 20 events after heavy compaction, got %d", count)
}
// Should have events 80-99 (most recent 20)
for i := 80; i < 100; i++ {
found := false
for _, e := range events {
if e.Observation.Observer == observer &&
e.Observation.Subject == subject &&
e.Observation.RestartNum == int64(i) {
found = true
break
}
}
if !found {
t.Errorf("Expected to find recent restart_num=%d, but it's missing", i)
}
}
// Should NOT have events 0-79
for i := 0; i < 80; i++ {
found := false
for _, e := range events {
if e.Observation.Observer == observer &&
e.Observation.Subject == subject &&
e.Observation.RestartNum == int64(i) {
found = true
break
}
}
if found {
t.Errorf("Old event restart_num=%d should have been compacted away", i)
}
}
}
// Test that compaction preserves critical importance events
func TestObservationCompaction_PreservesImportance(t *testing.T) {
ledger := NewSyncLedger(1000)
observer := types.NaraName("nara-a")
subject := types.NaraName("nara-b")
// Add 25 critical events (restarts and first-seen)
event := NewFirstSeenObservationEvent(observer, subject, time.Now().Unix())
ledger.AddEvent(event)
for i := 0; i < 24; i++ {
event := NewRestartObservationEvent(observer, subject, time.Now().Unix(), int64(i))
ledger.AddEvent(event)
}
// Should compact to 20 events, but all should still be critical
events := ledger.GetObservationEventsAbout(subject)
for _, e := range events {
if e.Observation.Observer == observer && e.Observation.Subject == subject {
if e.Observation.Importance != ImportanceCritical {
t.Errorf("Expected all compacted events to be Critical, got importance=%d", e.Observation.Importance)
}
}
}
}
// Test compaction with backfill events (after checkpoint exists)
func TestObservationCompaction_WithBackfill(t *testing.T) {
ledger := NewSyncLedger(1000)
observer := types.NaraName("nara-a")
subject := types.NaraName("nara-b")
// First add a checkpoint so compaction of restart events is allowed
checkpoint := NewTestCheckpointEvent(subject, time.Now().Unix()-3600, time.Now().Unix()-86400, 0, 0)
ledger.AddEvent(checkpoint)
// Add 15 backfill events
for i := 0; i < 15; i++ {
event := NewBackfillObservationEvent(observer, subject, time.Now().Unix(), int64(i), time.Now().Unix())
ledger.AddEvent(event)
}
// Add 10 regular restart events
for i := 15; i < 25; i++ {
event := NewRestartObservationEvent(observer, subject, time.Now().Unix(), int64(i))
ledger.AddEvent(event)
}
// Should have exactly 20 events (5 oldest backfills evicted)
events := ledger.GetObservationEventsAbout(subject)
count := 0
for _, e := range events {
if e.Observation.Observer == observer && e.Observation.Subject == subject {
count++
}
}
if count != 20 {
t.Errorf("Expected 20 events after backfill+regular compaction, got %d", count)
}
}