package logic import ( "strings" "testing" "github.com/gogf/gf/v2/os/gtime" ) // TestTombstoneValue 墓碑值由主键派生(前缀 + id):不同 id 必然不同、且以墓碑前缀开头, // 保证与真实值空间不相交(真实 openid/手机号不会以 "#" 开头)。 func TestTombstoneValue(t *testing.T) { if got := tombstoneShort(7); got != "#del7" { t.Fatalf("tombstoneShort(7)=%q want %q", got, "#del7") } if tombstoneShort(1) == tombstoneShort(2) { t.Fatal("不同 id 的墓碑值必须不同") } if !strings.HasPrefix(tombstoneShort(123), tombstonePrefix) { t.Fatal("墓碑值必须以墓碑前缀开头") } if !strings.HasPrefix(tombstoneLong(123), tombstonePrefix) { t.Fatal("长墓碑值必须以墓碑前缀开头") } if tombstoneLong(1) == tombstoneLong(2) { t.Fatal("不同 id 的长墓碑值必须不同") } } // TestTombstoneLong_Length 长墓碑长度必须 > 32(用户名空间外)且 <= 64(放得进 varchar(64))。 func TestTombstoneLong_Length(t *testing.T) { for _, id := range []int64{1, 5, 999, 9223372036854775} { n := len(tombstoneLong(id)) if n <= 32 { t.Fatalf("tombstoneLong(%d) 长度=%d,必须 >32 否则可被抢注", id, n) } if n > 64 { t.Fatalf("tombstoneLong(%d) 长度=%d,必须 <=64 否则写不进 varchar(64)", id, n) } } } // TestTombstoneForColumn username 走长墓碑(可被抢注列),phone / openid 走短墓碑。 func TestTombstoneForColumn(t *testing.T) { if got := tombstoneForColumn("username", 5); got != tombstoneLong(5) { t.Fatalf("username 应使用长墓碑,got %q", got) } if got := tombstoneForColumn("phone", 5); got != tombstoneShort(5) { t.Fatalf("phone 应使用短墓碑,got %q", got) } if got := tombstoneForColumn("openid", 5); got != tombstoneShort(5) { t.Fatalf("openid 应使用短墓碑,got %q", got) } } // TestTombstoneLong_NotSquattable 抢注通道关闭证明:任何**合法**用户名(长度 3-32, // 即 username 校验 required|length:3,32 允许的范围)都不可能等于任何长墓碑值。 func TestTombstoneLong_NotSquattable(t *testing.T) { longs := map[string]bool{} for _, id := range []int64{1, 5, 42, 999, 9223372036854775} { longs[tombstoneLong(id)] = true } // 枚举长度 3-32 的一组候选名,其中尤其包含「短墓碑名」——短墓碑名长度=len("#del5")=5,在合法区间内, // 但长墓碑长度 >32,绝不可能命中。 candidates := []string{"abc", "abcde", "#del5", "#del42", "#del999", strings.Repeat("a", 32)} for _, name := range candidates { if len(name) < 3 || len(name) > 32 { t.Fatalf("候选名 %q 不在合法区间 3-32", name) } if longs[name] { t.Fatalf("合法用户名 %q 命中长墓碑,抢注通道未关闭", name) } } } // TestDeletedFromJSON 原值快照:空 map / nil → nil(写 NULL);非空 → JSON 字符串且含原值。 func TestDeletedFromJSON(t *testing.T) { if v := deletedFromJSON(map[string]string{}); v != nil { t.Fatalf("空 map 应返回 nil,got %v", v) } if v := deletedFromJSON(nil); v != nil { t.Fatalf("nil 应返回 nil,got %v", v) } v := deletedFromJSON(map[string]string{"phone": "13800000000"}) s, ok := v.(string) if !ok { t.Fatalf("非空应返回 string,got %T", v) } if !strings.Contains(s, "13800000000") || !strings.Contains(s, "phone") { t.Fatalf("JSON 应包含原值,got %s", s) } } // TestAlreadySoftDeleted 幂等跳过判定:NULL / 零值时间视为未删;非零时间视为已删。 func TestAlreadySoftDeleted(t *testing.T) { if alreadySoftDeleted(nil) { t.Fatal("nil 应视为未删") } var zero gtime.Time if alreadySoftDeleted(&zero) { t.Fatal("零值时间应视为未删") } if !alreadySoftDeleted(gtime.Now()) { t.Fatal("非零时间应视为已删") } }