Skip to content

Commit 589615e

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

2 files changed

Lines changed: 251 additions & 0 deletions

File tree

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
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+
}
118+
119+
// Log full first event for inspection
120+
console.log(` First event structure:`, JSON.stringify(firstEvent, null, 2));
121+
} else {
122+
console.log(` ⚠️ No events returned for this project`);
123+
}
124+
} else {
125+
console.log(`❌ GET /export/events: ${basicResult.status}`);
126+
if (basicResult.error) console.log(` Error: ${basicResult.error}`);
127+
}
128+
129+
// Test 2: Events export with specific event filter
130+
console.log('\n\nTest 2: Events export with event filter');
131+
const filteredResult = await makeRequest('GET', '/export/events', {
132+
projectId: PROJECT_ID,
133+
event: 'screen_view',
134+
limit: 5,
135+
});
136+
results.push(filteredResult);
137+
138+
if (filteredResult.success) {
139+
console.log(`✅ GET /export/events (filtered): ${filteredResult.status}`);
140+
141+
if (filteredResult.data?.data?.length > 0) {
142+
const firstEvent = filteredResult.data.data[0];
143+
console.log(` Events returned: ${filteredResult.data.data.length}`);
144+
145+
if (firstEvent.properties !== undefined) {
146+
console.log(` ✅ Properties field present`);
147+
} else {
148+
console.log(` ❌ Properties field MISSING`);
149+
}
150+
} else {
151+
console.log(` ⚠️ No matching events found`);
152+
}
153+
} else {
154+
console.log(`❌ GET /export/events (filtered): ${filteredResult.status}`);
155+
}
156+
157+
// Test 3: Events export with profile include
158+
console.log('\n\nTest 3: Events export with profile include');
159+
const withProfileResult = await makeRequest('GET', '/export/events', {
160+
projectId: PROJECT_ID,
161+
includes: ['profile'],
162+
limit: 5,
163+
});
164+
results.push(withProfileResult);
165+
166+
if (withProfileResult.success) {
167+
console.log(`✅ GET /export/events (with profile): ${withProfileResult.status}`);
168+
169+
if (withProfileResult.data?.data?.length > 0) {
170+
const firstEvent = withProfileResult.data.data[0];
171+
172+
if (firstEvent.properties !== undefined) {
173+
console.log(` ✅ Properties field present`);
174+
} else {
175+
console.log(` ❌ Properties field MISSING`);
176+
}
177+
178+
if (firstEvent.profile !== undefined) {
179+
console.log(` ✅ Profile field present (included)`);
180+
} else {
181+
console.log(` ⚠️ Profile field not included (expected due to permissions)`);
182+
}
183+
}
184+
} else {
185+
console.log(`❌ GET /export/events (with profile): ${withProfileResult.status}`);
186+
}
187+
188+
// Test 4: Events export with date range
189+
console.log('\n\nTest 4: Events export with date range');
190+
const now = new Date();
191+
const weekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
192+
193+
const dateRangeResult = await makeRequest('GET', '/export/events', {
194+
projectId: PROJECT_ID,
195+
start: weekAgo.toISOString().split('T')[0],
196+
end: now.toISOString().split('T')[0],
197+
limit: 5,
198+
});
199+
results.push(dateRangeResult);
200+
201+
if (dateRangeResult.success) {
202+
console.log(`✅ GET /export/events (date range): ${dateRangeResult.status}`);
203+
204+
if (dateRangeResult.data?.data?.length > 0) {
205+
const firstEvent = dateRangeResult.data.data[0];
206+
207+
if (firstEvent.properties !== undefined) {
208+
console.log(` ✅ Properties field present`);
209+
} else {
210+
console.log(` ❌ Properties field MISSING`);
211+
}
212+
}
213+
} else {
214+
console.log(`❌ GET /export/events (date range): ${dateRangeResult.status}`);
215+
}
216+
}
217+
218+
async function main() {
219+
console.log(`🚀 Export API Test Suite`);
220+
console.log(`Using API_URL: ${API_BASE_URL}`);
221+
console.log(`Using PROJECT_ID: ${PROJECT_ID}`);
222+
223+
await testExportEvents();
224+
225+
// Summary
226+
console.log('\n\n📋 Test Summary');
227+
console.log('─'.repeat(50));
228+
229+
const passed = results.filter((r) => r.success).length;
230+
const failed = results.filter((r) => !r.success).length;
231+
232+
console.log(`Total Tests: ${results.length}`);
233+
console.log(`✅ Passed: ${passed}`);
234+
console.log(`❌ Failed: ${failed}`);
235+
236+
if (failed > 0) {
237+
console.log('\nFailed Tests:');
238+
results.filter((r) => !r.success).forEach((r) => {
239+
console.log(` - ${r.name}: ${r.error || r.status}`);
240+
});
241+
process.exit(1);
242+
}
243+
244+
console.log('\n✨ All tests passed!');
245+
}
246+
247+
main().catch((error) => {
248+
console.error('Test suite error:', error);
249+
process.exit(1);
250+
});

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)