···
+
"Coves/internal/atproto/identity"
+
// uniqueID generates a unique identifier for test isolation
+
func uniqueID() string {
+
return fmt.Sprintf("test-%d", time.Now().UnixNano())
+
// TestIdentityCache tests the PostgreSQL identity cache operations
+
func TestIdentityCache(t *testing.T) {
+
cache := identity.NewPostgresCache(db, 5*time.Minute)
+
ctx := context.Background()
+
// Generate unique test prefix for parallel safety
+
testID := fmt.Sprintf("test-%d", time.Now().UnixNano())
+
t.Run("Cache Miss on Empty Cache", func(t *testing.T) {
+
_, err := cache.Get(ctx, testID+"-nonexistent.test")
+
t.Error("Expected cache miss error, got nil")
+
t.Run("Set and Get Identity by Handle", func(t *testing.T) {
+
ident := &identity.Identity{
+
DID: "did:plc:" + testID + "-test123abc",
+
Handle: testID + "-alice.test",
+
PDSURL: "https://pds.alice.test",
+
ResolvedAt: time.Now().UTC(),
+
Method: identity.MethodHTTPS,
+
// Set identity in cache
+
if err := cache.Set(ctx, ident); err != nil {
+
t.Fatalf("Failed to cache identity: %v", err)
+
cached, err := cache.Get(ctx, ident.Handle)
+
t.Fatalf("Failed to get cached identity by handle: %v", err)
+
if cached.DID != ident.DID {
+
t.Errorf("Expected DID %s, got %s", ident.DID, cached.DID)
+
if cached.Handle != ident.Handle {
+
t.Errorf("Expected handle %s, got %s", ident.Handle, cached.Handle)
+
if cached.PDSURL != ident.PDSURL {
+
t.Errorf("Expected PDS URL %s, got %s", ident.PDSURL, cached.PDSURL)
+
t.Run("Get Identity by DID", func(t *testing.T) {
+
// Should be able to retrieve by DID as well (bidirectional cache)
+
expectedDID := "did:plc:" + testID + "-test123abc"
+
expectedHandle := testID + "-alice.test"
+
cached, err := cache.Get(ctx, expectedDID)
+
t.Fatalf("Failed to get cached identity by DID: %v", err)
+
if cached.Handle != expectedHandle {
+
t.Errorf("Expected handle %s, got %s", expectedHandle, cached.Handle)
+
t.Run("Update Existing Cache Entry", func(t *testing.T) {
+
// Update with new PDS URL
+
updated := &identity.Identity{
+
DID: "did:plc:test123abc",
+
PDSURL: "https://new-pds.alice.test",
+
ResolvedAt: time.Now(),
+
Method: identity.MethodHTTPS,
+
if err := cache.Set(ctx, updated); err != nil {
+
t.Fatalf("Failed to update cached identity: %v", err)
+
cached, err := cache.Get(ctx, "alice.test")
+
t.Fatalf("Failed to get updated identity: %v", err)
+
if cached.PDSURL != "https://new-pds.alice.test" {
+
t.Errorf("Expected updated PDS URL, got %s", cached.PDSURL)
+
t.Run("Delete Cache Entry", func(t *testing.T) {
+
if err := cache.Delete(ctx, "alice.test"); err != nil {
+
t.Fatalf("Failed to delete cache entry: %v", err)
+
// Should now be a cache miss
+
_, err := cache.Get(ctx, "alice.test")
+
t.Error("Expected cache miss after deletion, got nil error")
+
t.Run("Purge Removes Both Handle and DID Entries", func(t *testing.T) {
+
ident := &identity.Identity{
+
DID: "did:plc:purgetest",
+
PDSURL: "https://pds.purge.test",
+
ResolvedAt: time.Now(),
+
Method: identity.MethodDNS,
+
if err := cache.Set(ctx, ident); err != nil {
+
t.Fatalf("Failed to cache identity: %v", err)
+
// Verify both entries exist
+
if _, err := cache.Get(ctx, "purge.test"); err != nil {
+
t.Errorf("Handle entry should exist: %v", err)
+
if _, err := cache.Get(ctx, "did:plc:purgetest"); err != nil {
+
t.Errorf("DID entry should exist: %v", err)
+
if err := cache.Purge(ctx, "purge.test"); err != nil {
+
t.Fatalf("Failed to purge: %v", err)
+
if _, err := cache.Get(ctx, "purge.test"); err == nil {
+
t.Error("Handle entry should be purged")
+
if _, err := cache.Get(ctx, "did:plc:purgetest"); err == nil {
+
t.Error("DID entry should be purged")
+
t.Run("Handle Normalization - Case Insensitive", func(t *testing.T) {
+
ident := &identity.Identity{
+
DID: "did:plc:casetest",
+
PDSURL: "https://pds.alice.test",
+
ResolvedAt: time.Now(),
+
Method: identity.MethodHTTPS,
+
if err := cache.Set(ctx, ident); err != nil {
+
t.Fatalf("Failed to cache identity: %v", err)
+
// Should be retrievable with different casing
+
cached, err := cache.Get(ctx, "ALICE.TEST")
+
t.Fatalf("Failed to get identity with different casing: %v", err)
+
if cached.DID != "did:plc:casetest" {
+
t.Errorf("Expected DID did:plc:casetest, got %s", cached.DID)
+
cache.Delete(ctx, "alice.test")
+
t.Run("DID is Case Sensitive", func(t *testing.T) {
+
ident := &identity.Identity{
+
DID: "did:plc:CaseSensitive",
+
Handle: "sensitive.test",
+
PDSURL: "https://pds.test",
+
ResolvedAt: time.Now(),
+
Method: identity.MethodHTTPS,
+
if err := cache.Set(ctx, ident); err != nil {
+
t.Fatalf("Failed to cache identity: %v", err)
+
// Should retrieve with exact case
+
if _, err := cache.Get(ctx, "did:plc:CaseSensitive"); err != nil {
+
t.Errorf("Should retrieve DID with exact case: %v", err)
+
// Different case should miss (DIDs are case-sensitive)
+
if _, err := cache.Get(ctx, "did:plc:casesensitive"); err == nil {
+
t.Error("Should NOT retrieve DID with different case")
+
cache.Delete(ctx, "did:plc:CaseSensitive")
+
// TestIdentityCacheTTL tests that expired cache entries are not returned
+
func TestIdentityCacheTTL(t *testing.T) {
+
// Create cache with very short TTL (reduced from 1s to 100ms for faster, less flaky tests)
+
ttl := 100 * time.Millisecond
+
cache := identity.NewPostgresCache(db, ttl)
+
ctx := context.Background()
+
// Use unique ID for test isolation
+
ident := &identity.Identity{
+
DID: "did:plc:" + testID,
+
Handle: testID + ".ttl.test",
+
PDSURL: "https://pds.ttl.test",
+
ResolvedAt: time.Now().UTC(),
+
Method: identity.MethodHTTPS,
+
if err := cache.Set(ctx, ident); err != nil {
+
t.Fatalf("Failed to cache identity: %v", err)
+
// Should be retrievable immediately
+
if _, err := cache.Get(ctx, ident.Handle); err != nil {
+
t.Errorf("Should retrieve fresh cache entry: %v", err)
+
// Wait for TTL to expire (1.5x TTL for safety margin on slow systems)
+
waitTime := time.Duration(float64(ttl) * 1.5)
+
t.Logf("Waiting %s for cache entry to expire (TTL=%s)...", waitTime, ttl)
+
// Should now be a cache miss
+
_, err := cache.Get(ctx, ident.Handle)
+
t.Error("Expected cache miss after TTL expiration, got nil error")
+
// TestIdentityResolverWithCache tests the caching resolver behavior
+
func TestIdentityResolverWithCache(t *testing.T) {
+
cache := identity.NewPostgresCache(db, 5*time.Minute)
+
_, _ = db.Exec("TRUNCATE identity_cache")
+
// Create resolver with caching
+
resolver := identity.NewResolver(db, identity.Config{
+
PLCURL: "https://plc.directory",
+
CacheTTL: 5 * time.Minute,
+
ctx := context.Background()
+
t.Run("Resolve Invalid Identifier", func(t *testing.T) {
+
_, err := resolver.Resolve(ctx, "")
+
t.Error("Expected error for empty identifier")
+
_, err = resolver.Resolve(ctx, "invalid format")
+
t.Error("Expected error for invalid identifier format")
+
t.Run("ResolveHandle Returns DID and PDS URL", func(t *testing.T) {
+
// Pre-populate cache with known identity
+
ident := &identity.Identity{
+
DID: "did:plc:resolvetest",
+
Handle: "resolve.test",
+
PDSURL: "https://pds.resolve.test",
+
ResolvedAt: time.Now(),
+
Method: identity.MethodDNS,
+
if err := cache.Set(ctx, ident); err != nil {
+
t.Fatalf("Failed to pre-populate cache: %v", err)
+
did, pdsURL, err := resolver.ResolveHandle(ctx, "resolve.test")
+
t.Fatalf("Failed to resolve handle: %v", err)
+
if did != "did:plc:resolvetest" {
+
t.Errorf("Expected DID did:plc:resolvetest, got %s", did)
+
if pdsURL != "https://pds.resolve.test" {
+
t.Errorf("Expected PDS URL https://pds.resolve.test, got %s", pdsURL)
+
t.Run("Purge Removes from Cache", func(t *testing.T) {
+
ident := &identity.Identity{
+
DID: "did:plc:purge123",
+
Handle: "purgetest.test",
+
PDSURL: "https://pds.test",
+
ResolvedAt: time.Now(),
+
Method: identity.MethodHTTPS,
+
if err := cache.Set(ctx, ident); err != nil {
+
t.Fatalf("Failed to cache identity: %v", err)
+
if _, err := cache.Get(ctx, "purgetest.test"); err != nil {
+
t.Fatalf("Identity should be cached: %v", err)
+
if err := resolver.Purge(ctx, "purgetest.test"); err != nil {
+
t.Fatalf("Failed to purge: %v", err)
+
// Should be gone from cache
+
if _, err := cache.Get(ctx, "purgetest.test"); err == nil {
+
t.Error("Identity should be purged from cache")
+
// TestIdentityResolverRealHandles tests resolution with real atProto handles
+
// This is an optional integration test that requires network access
+
func TestIdentityResolverRealHandles(t *testing.T) {
+
t.Skip("Skipping real handle resolution test in short mode")
+
// Skip if environment variable is not set (opt-in for real network tests)
+
if os.Getenv("TEST_REAL_HANDLES") != "1" {
+
t.Skip("Skipping real handle resolution - set TEST_REAL_HANDLES=1 to enable")
+
resolver := identity.NewResolver(db, identity.Config{
+
PLCURL: "https://plc.directory",
+
CacheTTL: 10 * time.Minute,
+
ctx := context.Background()
+
testCases := []struct {
+
expectedMethod identity.ResolutionMethod
+
name: "Resolve bsky.app (well-known handle)",
+
expectedMethod: identity.MethodHTTPS,
+
name: "Resolve nonexistent handle",
+
handle: "this-handle-definitely-does-not-exist-12345.bsky.social",
+
for _, tc := range testCases {
+
t.Run(tc.name, func(t *testing.T) {
+
ident, err := resolver.Resolve(ctx, tc.handle)
+
t.Error("Expected error for nonexistent handle")
+
t.Fatalf("Failed to resolve handle %s: %v", tc.handle, err)
+
if ident.Handle != tc.handle {
+
t.Errorf("Expected handle %s, got %s", tc.handle, ident.Handle)
+
t.Error("Expected non-empty DID")
+
if ident.PDSURL == "" {
+
t.Error("Expected non-empty PDS URL")
+
t.Logf("✅ Resolved %s → %s (PDS: %s, Method: %s)",
+
ident.Handle, ident.DID, ident.PDSURL, ident.Method)
+
// Second resolution should hit cache
+
ident2, err := resolver.Resolve(ctx, tc.handle)
+
t.Fatalf("Failed second resolution: %v", err)
+
if ident2.Method != identity.MethodCache {
+
t.Errorf("Second resolution should be from cache, got method: %s", ident2.Method)
+
t.Logf("✅ Second resolution from cache: %s (Method: %s)", tc.handle, ident2.Method)
+
// TestResolveDID tests DID document resolution
+
func TestResolveDID(t *testing.T) {
+
t.Skip("Skipping DID resolution test in short mode")
+
if os.Getenv("TEST_REAL_HANDLES") != "1" {
+
t.Skip("Skipping DID resolution - set TEST_REAL_HANDLES=1 to enable")
+
resolver := identity.NewResolver(db, identity.Config{
+
PLCURL: "https://plc.directory",
+
CacheTTL: 10 * time.Minute,
+
ctx := context.Background()
+
t.Run("Resolve Real DID Document", func(t *testing.T) {
+
// First resolve a handle to get a real DID
+
ident, err := resolver.Resolve(ctx, "bsky.app")
+
t.Skipf("Failed to resolve handle for DID test: %v", err)
+
// Now resolve the DID document
+
doc, err := resolver.ResolveDID(ctx, ident.DID)
+
t.Fatalf("Failed to resolve DID document: %v", err)
+
if doc.DID != ident.DID {
+
t.Errorf("Expected DID %s, got %s", ident.DID, doc.DID)
+
// Should have at least PDS service
+
if len(doc.Service) == 0 {
+
t.Error("Expected at least one service in DID document")
+
for _, svc := range doc.Service {
+
if svc.Type == "AtprotoPersonalDataServer" {
+
if svc.ServiceEndpoint == "" {
+
t.Error("PDS service endpoint should not be empty")
+
t.Logf("✅ PDS Service: %s", svc.ServiceEndpoint)
+
t.Error("Expected to find AtprotoPersonalDataServer service in DID document")
+
t.Run("Resolve Invalid DID", func(t *testing.T) {
+
_, err := resolver.ResolveDID(ctx, "not-a-did")
+
t.Error("Expected error for invalid DID format")