Skip to content

Commit 5374dc2

Browse files
committed
fix: Include properties in export/events Payload
1 parent f8f470a commit 5374dc2

2 files changed

Lines changed: 255 additions & 0 deletions

File tree

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
/**
2+
* Script to test the Export API endpoints
3+
*
4+
* Specifically tests that the /export/events endpoint includes event properties in the payload
5+
*
6+
* Usage:
7+
* pnpm jiti scripts/test-export-api.ts
8+
*
9+
* Environment variables:
10+
* CLIENT_ID: Export API client ID (with read or root permissions)
11+
* CLIENT_SECRET: Export API client secret
12+
* PROJECT_ID: Project ID to test against
13+
* API_URL: API base URL (default: http://localhost:3333)
14+
*/
15+
16+
const CLIENT_ID = process.env.CLIENT_ID!;
17+
const CLIENT_SECRET = process.env.CLIENT_SECRET!;
18+
const PROJECT_ID = process.env.PROJECT_ID!;
19+
const API_BASE_URL = process.env.API_URL || 'http://localhost:3333';
20+
21+
if (!CLIENT_ID || !CLIENT_SECRET || !PROJECT_ID) {
22+
console.error('CLIENT_ID, CLIENT_SECRET, and PROJECT_ID must be set');
23+
process.exit(1);
24+
}
25+
26+
interface TestResult {
27+
name: string;
28+
method: string;
29+
url: string;
30+
status: number;
31+
success: boolean;
32+
error?: string;
33+
data?: any;
34+
}
35+
36+
const results: TestResult[] = [];
37+
38+
async function makeRequest(
39+
method: string,
40+
path: string,
41+
params?: Record<string, any>,
42+
): Promise<TestResult> {
43+
let url = `${API_BASE_URL}${path}`;
44+
45+
if (params && method === 'GET') {
46+
const searchParams = new URLSearchParams();
47+
for (const [key, value] of Object.entries(params)) {
48+
if (Array.isArray(value)) {
49+
searchParams.append(key, JSON.stringify(value));
50+
} else if (value instanceof Object) {
51+
searchParams.append(key, JSON.stringify(value));
52+
} else if (value !== undefined && value !== null) {
53+
searchParams.append(key, String(value));
54+
}
55+
}
56+
url += '?' + searchParams.toString();
57+
}
58+
59+
const headers: Record<string, string> = {
60+
'openpanel-client-id': CLIENT_ID,
61+
'openpanel-client-secret': CLIENT_SECRET,
62+
};
63+
64+
try {
65+
const response = await fetch(url, {
66+
method,
67+
headers,
68+
});
69+
70+
const data = await response.json().catch(() => ({}));
71+
72+
return {
73+
name: `${method} ${path}`,
74+
method,
75+
url,
76+
status: response.status,
77+
success: response.ok,
78+
error: response.ok ? undefined : data.message || 'Request failed',
79+
data: response.ok ? data : undefined,
80+
};
81+
} catch (error) {
82+
return {
83+
name: `${method} ${path}`,
84+
method,
85+
url,
86+
status: 0,
87+
success: false,
88+
error: error instanceof Error ? error.message : 'Unknown error',
89+
};
90+
}
91+
}
92+
93+
async function testExportEvents() {
94+
console.log('\n📊 Testing Export Events endpoint...\n');
95+
96+
// Test 1: Basic events export without includes
97+
console.log('Test 1: Basic events export (should include properties by default)');
98+
const basicResult = await makeRequest('GET', '/export/events', {
99+
projectId: PROJECT_ID,
100+
limit: 10,
101+
});
102+
results.push(basicResult);
103+
104+
if (basicResult.success) {
105+
console.log(`✅ GET /export/events: ${basicResult.status}`);
106+
107+
if (basicResult.data?.data?.length > 0) {
108+
const firstEvent = basicResult.data.data[0];
109+
console.log(` Total events returned: ${basicResult.data.data.length}`);
110+
111+
// Check for properties field
112+
if (firstEvent.properties !== undefined) {
113+
console.log(` ✅ Properties field present: ${JSON.stringify(firstEvent.properties)}`);
114+
} else {
115+
console.log(` ❌ Properties field MISSING in event`);
116+
console.log(` Event keys: ${Object.keys(firstEvent).join(', ')}`);
117+
throw new Error('Test 1 FAILED: Properties field is missing from export/events response');
118+
}
119+
120+
// Log full first event for inspection
121+
console.log(` First event structure:`, JSON.stringify(firstEvent, null, 2));
122+
} else {
123+
console.log(` ⚠️ No events returned for this project`);
124+
}
125+
} else {
126+
console.log(`❌ GET /export/events: ${basicResult.status}`);
127+
if (basicResult.error) console.log(` Error: ${basicResult.error}`);
128+
}
129+
130+
// Test 2: Events export with specific event filter
131+
console.log('\n\nTest 2: Events export with event filter');
132+
const filteredResult = await makeRequest('GET', '/export/events', {
133+
projectId: PROJECT_ID,
134+
event: 'screen_view',
135+
limit: 5,
136+
});
137+
results.push(filteredResult);
138+
139+
if (filteredResult.success) {
140+
console.log(`✅ GET /export/events (filtered): ${filteredResult.status}`);
141+
142+
if (filteredResult.data?.data?.length > 0) {
143+
const firstEvent = filteredResult.data.data[0];
144+
console.log(` Events returned: ${filteredResult.data.data.length}`);
145+
146+
if (firstEvent.properties !== undefined) {
147+
console.log(` ✅ Properties field present`);
148+
} else {
149+
console.log(` ❌ Properties field MISSING`);
150+
throw new Error('Test 2 FAILED: Properties field is missing from filtered export/events response');
151+
}
152+
} else {
153+
console.log(` ⚠️ No matching events found`);
154+
}
155+
} else {
156+
console.log(`❌ GET /export/events (filtered): ${filteredResult.status}`);
157+
}
158+
159+
// Test 3: Events export with profile include
160+
console.log('\n\nTest 3: Events export with profile include');
161+
const withProfileResult = await makeRequest('GET', '/export/events', {
162+
projectId: PROJECT_ID,
163+
includes: ['profile'],
164+
limit: 5,
165+
});
166+
results.push(withProfileResult);
167+
168+
if (withProfileResult.success) {
169+
console.log(`✅ GET /export/events (with profile): ${withProfileResult.status}`);
170+
171+
if (withProfileResult.data?.data?.length > 0) {
172+
const firstEvent = withProfileResult.data.data[0];
173+
174+
if (firstEvent.properties !== undefined) {
175+
console.log(` ✅ Properties field present`);
176+
} else {
177+
console.log(` ❌ Properties field MISSING`);
178+
throw new Error('Test 3 FAILED: Properties field is missing from export/events response with profile include');
179+
}
180+
181+
if (firstEvent.profile !== undefined) {
182+
console.log(` ✅ Profile field present (included)`);
183+
} else {
184+
console.log(` ⚠️ Profile field not included (expected due to permissions)`);
185+
}
186+
}
187+
} else {
188+
console.log(`❌ GET /export/events (with profile): ${withProfileResult.status}`);
189+
}
190+
191+
// Test 4: Events export with date range
192+
console.log('\n\nTest 4: Events export with date range');
193+
const now = new Date();
194+
const weekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
195+
196+
const dateRangeResult = await makeRequest('GET', '/export/events', {
197+
projectId: PROJECT_ID,
198+
start: weekAgo.toISOString().split('T')[0],
199+
end: now.toISOString().split('T')[0],
200+
limit: 5,
201+
});
202+
results.push(dateRangeResult);
203+
204+
if (dateRangeResult.success) {
205+
console.log(`✅ GET /export/events (date range): ${dateRangeResult.status}`);
206+
207+
if (dateRangeResult.data?.data?.length > 0) {
208+
const firstEvent = dateRangeResult.data.data[0];
209+
210+
if (firstEvent.properties !== undefined) {
211+
console.log(` ✅ Properties field present`);
212+
} else {
213+
console.log(` ❌ Properties field MISSING`);
214+
throw new Error('Test 4 FAILED: Properties field is missing from export/events response with date range');
215+
}
216+
}
217+
} else {
218+
console.log(`❌ GET /export/events (date range): ${dateRangeResult.status}`);
219+
}
220+
}
221+
222+
async function main() {
223+
console.log(`🚀 Export API Test Suite`);
224+
console.log(`Using API_URL: ${API_BASE_URL}`);
225+
console.log(`Using PROJECT_ID: ${PROJECT_ID}`);
226+
227+
await testExportEvents();
228+
229+
// Summary
230+
console.log('\n\n📋 Test Summary');
231+
console.log('─'.repeat(50));
232+
233+
const passed = results.filter((r) => r.success).length;
234+
const failed = results.filter((r) => !r.success).length;
235+
236+
console.log(`Total Tests: ${results.length}`);
237+
console.log(`✅ Passed: ${passed}`);
238+
console.log(`❌ Failed: ${failed}`);
239+
240+
if (failed > 0) {
241+
console.log('\nFailed Tests:');
242+
results.filter((r) => !r.success).forEach((r) => {
243+
console.log(` - ${r.name}: ${r.error || r.status}`);
244+
});
245+
process.exit(1);
246+
}
247+
248+
console.log('\n✨ All tests passed!');
249+
}
250+
251+
main().catch((error) => {
252+
console.error('Test suite error:', error);
253+
process.exit(1);
254+
});

apps/api/src/controllers/export.controller.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ export async function events(
114114
take,
115115
profileId: query.data.profileId,
116116
select: {
117+
properties: true,
117118
profile: false,
118119
meta: false,
119120
...query.data.includes?.reduce(

0 commit comments

Comments
 (0)