package infra import ( "database/sql" "os" "path/filepath" "testing" _ "github.com/mattn/go-sqlite3" ) const wgRevokeTestConfig = `[Interface] Address = 10.0.0.1/24 PrivateKey = SERVERKEY== # DeviceID:device-revoke-001 [Peer] PublicKey = PUBKEYREVOKE001== AllowedIPs = 10.0.0.10/32 # DeviceID:device-revoke-002 [Peer] PublicKey = PUBKEYREVOKE002== AllowedIPs = 10.0.0.11/32 ` func TestWGPeerRevoke(t *testing.T) { origSyncConf := wgSyncConfFn wgSyncConfFn = func(iface, configPath string) error { return nil } defer func() { wgSyncConfFn = origSyncConf }() origBlacklist := wgAppendBlacklistFn wgAppendBlacklistFn = func(line string) error { return nil } defer func() { wgAppendBlacklistFn = origBlacklist }() t.Run("audit DB contiene registro con this_hash != prev_hash", func(t *testing.T) { dir := t.TempDir() configPath := filepath.Join(dir, "wg0.conf") auditDBPath := filepath.Join(dir, "revoked.db") if err := os.WriteFile(configPath, []byte(wgRevokeTestConfig), 0600); err != nil { t.Fatalf("write config: %v", err) } audit, err := WGPeerRevoke("device-revoke-001", "operator-alice", "dispositivo perdido", configPath, auditDBPath) if err != nil { t.Fatalf("unexpected error: %v", err) } if audit.ThisHash == "" { t.Error("this_hash is empty") } // En el primer registro prev_hash es vacio — this_hash debe diferir siempre. if audit.ThisHash == audit.PrevHash { t.Errorf("this_hash == prev_hash (%q), expected different values", audit.ThisHash) } if audit.PublicKey != "PUBKEYREVOKE001==" { t.Errorf("public_key=%q, want PUBKEYREVOKE001==", audit.PublicKey) } // Verificar en la BD directamente. db, err := sql.Open("sqlite3", auditDBPath) if err != nil { t.Fatalf("open audit db: %v", err) } defer db.Close() var storedHash, storedPubKey string if err := db.QueryRow("SELECT this_hash, public_key FROM revoked_peers WHERE device_id = ?", "device-revoke-001").Scan(&storedHash, &storedPubKey); err != nil { t.Fatalf("query audit record: %v", err) } if storedHash != audit.ThisHash { t.Errorf("stored hash=%q, want %q", storedHash, audit.ThisHash) } if storedPubKey != "PUBKEYREVOKE001==" { t.Errorf("stored public_key=%q, want PUBKEYREVOKE001==", storedPubKey) } }) t.Run("segunda revoke del mismo peer → error already revoked", func(t *testing.T) { dir := t.TempDir() configPath := filepath.Join(dir, "wg0.conf") auditDBPath := filepath.Join(dir, "revoked.db") if err := os.WriteFile(configPath, []byte(wgRevokeTestConfig), 0600); err != nil { t.Fatalf("write config: %v", err) } // Primera revocacion. if _, err := WGPeerRevoke("device-revoke-002", "operator-bob", "comprometido", configPath, auditDBPath); err != nil { t.Fatalf("first revoke unexpected error: %v", err) } // Segunda revocacion del mismo deviceID → debe fallar por audit DB, no por config. _, err := WGPeerRevoke("device-revoke-002", "operator-bob", "segundo intento", configPath, auditDBPath) if err == nil { t.Fatal("expected error on second revoke, got nil") } if !contains(err.Error(), "already revoked") { t.Errorf("expected 'already revoked' error, got: %v", err) } }) }