|
| 1 | +package table |
| 2 | + |
| 3 | +import ( |
| 4 | + "reflect" |
| 5 | + "testing" |
| 6 | +) |
| 7 | + |
| 8 | +func TestSanityTableBy(t *testing.T) { |
| 9 | + // helper to assert panic |
| 10 | + expectsPanic := func(name string, fn func()) { |
| 11 | + t.Helper() |
| 12 | + defer func() { |
| 13 | + if r := recover(); r == nil { |
| 14 | + t.Errorf("%s: expected panic, but none occurred", name) |
| 15 | + } |
| 16 | + }() |
| 17 | + fn() |
| 18 | + } |
| 19 | + |
| 20 | + // 1. QueryBy should panic on nil or empty filters |
| 21 | + expectsPanic("QueryBy(nil)", func() { (&Table{}).QueryBy(nil) }) |
| 22 | + expectsPanic("QueryBy(empty)", func() { (&Table{}).QueryBy(map[int]string{}) }) |
| 23 | + |
| 24 | + // 2. DeleteBy should panic on nil or empty filters |
| 25 | + expectsPanic("DeleteBy(nil)", func() { (&Table{}).DeleteBy(nil) }) |
| 26 | + expectsPanic("DeleteBy(empty)", func() { (&Table{}).DeleteBy(map[int]string{}) }) |
| 27 | + |
| 28 | + // Prepare a table and multi-bucket scenario |
| 29 | + tbl := &Table{} |
| 30 | + // bucket1: simple two-column rows |
| 31 | + tbl.Insert([][]string{ |
| 32 | + {"u1", "admin", "active"}, |
| 33 | + {"u2", "member", "inactive"}, |
| 34 | + {"u3", "admin", "inactive"}, |
| 35 | + }) |
| 36 | + // bucket2: overlapping and new data, including holes |
| 37 | + tbl.InsertHoles([][]string{ |
| 38 | + {"u4", "member", "active"}, |
| 39 | + nil, // hole row |
| 40 | + {"u5", "guest", "active"}, |
| 41 | + }) |
| 42 | + // bucket3: more data |
| 43 | + tbl.Insert([][]string{ |
| 44 | + {"u6", "guest", "inactive"}, |
| 45 | + {"u7", "admin", "active"}, |
| 46 | + }) |
| 47 | + |
| 48 | + // 3. QueryBy no-match returns nil |
| 49 | + if got := tbl.QueryBy(map[int]string{0: "noone"}); got != nil { |
| 50 | + t.Errorf("QueryBy(no-match) = %v; want nil", got) |
| 51 | + } |
| 52 | + |
| 53 | + // 4. QueryBy single-col matches across buckets |
| 54 | + adminRows := tbl.QueryBy(map[int]string{1: "admin"}) |
| 55 | + if len(adminRows) != 3 { |
| 56 | + t.Fatalf("QueryBy(admin) count = %d; want 3", len(adminRows)) |
| 57 | + } |
| 58 | + // Verify all returned rows have "admin" in col 1 |
| 59 | + for _, r := range adminRows { |
| 60 | + if r[1] != "admin" { |
| 61 | + t.Errorf("QueryBy(admin) returned non-admin row %v", r) |
| 62 | + } |
| 63 | + } |
| 64 | + |
| 65 | + // 5. QueryBy multi-col filter |
| 66 | + activeAdmin := tbl.QueryBy(map[int]string{1: "admin", 2: "active"}) |
| 67 | + want := [][]string{{"u1", "admin", "active"}, {"u7", "admin", "active"}} |
| 68 | + if !reflect.DeepEqual(activeAdmin, want) { |
| 69 | + t.Errorf("QueryBy(admin+active) = %v; want %v", activeAdmin, want) |
| 70 | + } |
| 71 | + |
| 72 | + // 6. Ensure QueryBy does not mutate underlying data |
| 73 | + _ = tbl.QueryBy(map[int]string{2: "inactive"}) |
| 74 | + allBefore := tbl.All() |
| 75 | + tbl.QueryBy(map[int]string{2: "inactive"}) |
| 76 | + allAfter := tbl.All() |
| 77 | + if !reflect.DeepEqual(allBefore, allAfter) { |
| 78 | + t.Errorf("QueryBy mutated table: before=%v after=%v", allBefore, allAfter) |
| 79 | + } |
| 80 | + |
| 81 | + // 7. DeleteBy removes matching rows |
| 82 | + deleted := tbl.DeleteBy(map[int]string{2: "inactive"}) |
| 83 | + // We had u2, u3, u6 as inactive → 3 deletions |
| 84 | + if deleted != 3 { |
| 85 | + t.Errorf("DeleteBy(inactive) deleted %d; want 3", deleted) |
| 86 | + } |
| 87 | + // QueryBy should no longer return any inactive |
| 88 | + if got := tbl.QueryBy(map[int]string{2: "inactive"}); got != nil { |
| 89 | + t.Errorf("after DeleteBy, QueryBy(inactive) = %v; want nil", got) |
| 90 | + } |
| 91 | + // Count of any active should remain |
| 92 | + if got := tbl.QueryBy(map[int]string{2: "active"}); len(got) == 0 { |
| 93 | + t.Error("after DeleteBy(inactive), no active rows found; expected some") |
| 94 | + } |
| 95 | + |
| 96 | + // 8. DeleteBy on overlapping filters |
| 97 | + // Remove everyone with role="admin" |
| 98 | + del2 := tbl.DeleteBy(map[int]string{1: "admin"}) |
| 99 | + // Only u1 and u7 were admin+active originally; they should now be deleted |
| 100 | + if del2 != 2 { |
| 101 | + t.Errorf("DeleteBy(admin) deleted %d; want 2", del2) |
| 102 | + } |
| 103 | + remaining := tbl.All() |
| 104 | + // Remaining should be only non-admin, non-inactive: u4 and u5 (and possibly holes already filtered) |
| 105 | + for _, r := range remaining { |
| 106 | + if r[1] == "admin" || r[2] == "inactive" { |
| 107 | + t.Errorf("bad remaining row after DeleteBy: %v", r) |
| 108 | + } |
| 109 | + } |
| 110 | + |
| 111 | + // 9. DeleteBy complete cleanup |
| 112 | + // Now delete all remaining active members |
| 113 | + del3 := tbl.DeleteBy(map[int]string{2: "active"}) |
| 114 | + if del3 == 0 { |
| 115 | + t.Errorf("DeleteBy(active) deleted %d; expected >0", del3) |
| 116 | + } |
| 117 | + if any := tbl.All(); len(any) != 0 { |
| 118 | + t.Errorf("table not empty after final DeleteBy: %v", any) |
| 119 | + } |
| 120 | +} |
0 commit comments