MCPcopy Create free account
hub / github.com/Mnexa-AI/e2a / TestExportUserData

Function TestExportUserData

internal/identity/user_data_rights_test.go:93–168  ·  view source on GitHub ↗

TestExportUserData verifies the right-of-access flow. The export should contain the user's profile, every domain/agent/key/message they own, and exclude internal identifiers (google_subject, key hashes, session tokens).

(t *testing.T)

Source from the content-addressed store, hash-verified

91// they own, and exclude internal identifiers (google_subject, key
92// hashes, session tokens).
93func TestExportUserData(t *testing.T) {
94 pool := testutil.TestDB(t)
95 store := identity.NewStore(pool)
96 ctx := context.Background()
97
98 user := seedUserData(t, store, ctx, "exporter")
99
100 dump, err := store.ExportUserData(ctx, user.ID)
101 if err != nil {
102 t.Fatalf("ExportUserData: %v", err)
103 }
104
105 if dump.User.ID != user.ID || dump.User.Email != user.Email {
106 t.Errorf("user mismatch: got id=%s email=%s, want %s/%s",
107 dump.User.ID, dump.User.Email, user.ID, user.Email)
108 }
109 if len(dump.Domains) != 1 {
110 t.Errorf("domains: got %d, want 1", len(dump.Domains))
111 }
112 if len(dump.Agents) != 1 {
113 t.Errorf("agents: got %d, want 1", len(dump.Agents))
114 }
115 if len(dump.APIKeys) != 2 {
116 t.Errorf("api_keys: got %d, want 2", len(dump.APIKeys))
117 }
118 if len(dump.Messages) != 2 {
119 t.Errorf("messages: got %d, want 2 (1 inbound + 1 outbound)", len(dump.Messages))
120 }
121 if len(dump.Suppressions) != 1 || dump.Suppressions[0].Address != "blocked@spam.com" {
122 t.Errorf("suppressions: got %+v, want 1 (blocked@spam.com)", dump.Suppressions)
123 }
124 if len(dump.ProtectionEvents) != 1 || dump.ProtectionEvents[0].SubjectAddr != "alice@gmail.com" {
125 t.Errorf("protection_events: got %+v, want 1 (subject alice@gmail.com)", dump.ProtectionEvents)
126 }
127
128 // Right-of-access requires every stored header field to round-trip
129 // through the export. Reply-To regression-guard: if a future SELECT
130 // drops the column, the user's export silently loses data — exactly
131 // the kind of gap that fails a data-rights audit.
132 var inbound *identity.Message
133 for i := range dump.Messages {
134 if dump.Messages[i].Direction == "inbound" {
135 inbound = &dump.Messages[i]
136 break
137 }
138 }
139 if inbound == nil {
140 t.Fatal("no inbound message in export")
141 }
142 wantReplyTo := []string{"real-alice@example.com"}
143 if !reflect.DeepEqual(inbound.ReplyTo, wantReplyTo) {
144 t.Errorf("inbound ReplyTo in export = %v, want %v", inbound.ReplyTo, wantReplyTo)
145 }
146
147 // Confirm the export doesn't leak internal identifiers. We marshal
148 // to JSON because the most likely accidental leak path is a struct
149 // field with a `json:` tag we forgot.
150 raw, err := json.Marshal(dump)

Callers

nothing calls this directly

Calls 6

ExportUserDataMethod · 0.95
TestDBFunction · 0.92
NewStoreFunction · 0.92
seedUserDataFunction · 0.85
jsonContainsFunction · 0.85
ErrorMethod · 0.45

Tested by

no test coverage detected