ATlast — you'll never need to find your favorites on another platform again. Find your favs in the ATmosphere.
atproto
16
fork

Configure Feed

Select the types of activity you want to include in your feed.

test(api): add extension import tests

- test auth, validation, error handling
- validate pages
- test platform support
- test edge cases: lrg arr, response struc

byarielm.fyi 43e13713 e4b06075

verified
+356
+356
packages/api/__tests__/routes/extension.test.ts
··· 1 + /** 2 + * Extension API Integration Tests 3 + * 4 + * Tests browser extension import functionality. 5 + */ 6 + 7 + import { describe, it, expect, beforeAll, afterAll } from 'vitest'; 8 + import { 9 + request, 10 + authRequest, 11 + requestWithSession, 12 + parseResponse, 13 + testId, 14 + } from '../helpers'; 15 + import { 16 + createTestSession, 17 + cleanupAllTestSessions, 18 + } from '../fixtures'; 19 + 20 + describe('Extension API', () => { 21 + let validSession: string; 22 + 23 + beforeAll(async () => { 24 + validSession = await createTestSession('standard'); 25 + }); 26 + 27 + afterAll(async () => { 28 + await cleanupAllTestSessions(); 29 + }); 30 + 31 + describe('POST /api/extension/import', () => { 32 + it('returns 401 without authentication', async () => { 33 + const res = await request('/api/extension/import', { 34 + method: 'POST', 35 + body: JSON.stringify({ 36 + platform: 'instagram', 37 + usernames: ['testuser'], 38 + metadata: { 39 + extensionVersion: '1.0.0', 40 + scrapedAt: new Date().toISOString(), 41 + pageType: 'following', 42 + sourceUrl: 'https://instagram.com/testuser/following', 43 + }, 44 + }), 45 + }); 46 + expect(res.status).toBe(401); 47 + }); 48 + 49 + it('returns 400 with missing platform', async () => { 50 + const res = await authRequest('/api/extension/import', { 51 + method: 'POST', 52 + body: JSON.stringify({ 53 + usernames: ['testuser'], 54 + metadata: { 55 + extensionVersion: '1.0.0', 56 + scrapedAt: new Date().toISOString(), 57 + pageType: 'following', 58 + sourceUrl: 'https://instagram.com/testuser/following', 59 + }, 60 + }), 61 + }); 62 + expect(res.status).toBe(400); 63 + 64 + const body = await parseResponse(res); 65 + expect(body.success).toBe(false); 66 + expect(body.error).toContain('Invalid request'); 67 + }); 68 + 69 + it('returns 400 with empty usernames array', async () => { 70 + const res = await authRequest('/api/extension/import', { 71 + method: 'POST', 72 + body: JSON.stringify({ 73 + platform: 'instagram', 74 + usernames: [], 75 + metadata: { 76 + extensionVersion: '1.0.0', 77 + scrapedAt: new Date().toISOString(), 78 + pageType: 'following', 79 + sourceUrl: 'https://instagram.com/testuser/following', 80 + }, 81 + }), 82 + }); 83 + expect(res.status).toBe(400); 84 + 85 + const body = await parseResponse(res); 86 + expect(body.success).toBe(false); 87 + expect(body.error).toContain('Invalid request'); 88 + }); 89 + 90 + it('returns 400 with too many usernames (>10000)', async () => { 91 + const tooManyUsernames = Array.from({ length: 10001 }, (_, i) => `user${i}`); 92 + 93 + const res = await authRequest('/api/extension/import', { 94 + method: 'POST', 95 + body: JSON.stringify({ 96 + platform: 'instagram', 97 + usernames: tooManyUsernames, 98 + metadata: { 99 + extensionVersion: '1.0.0', 100 + scrapedAt: new Date().toISOString(), 101 + pageType: 'following', 102 + sourceUrl: 'https://instagram.com/testuser/following', 103 + }, 104 + }), 105 + }); 106 + expect(res.status).toBe(400); 107 + 108 + const body = await parseResponse(res); 109 + expect(body.success).toBe(false); 110 + expect(body.error).toContain('1-10000'); 111 + }); 112 + 113 + it('returns 400 with missing metadata', async () => { 114 + const res = await authRequest('/api/extension/import', { 115 + method: 'POST', 116 + body: JSON.stringify({ 117 + platform: 'instagram', 118 + usernames: ['testuser'], 119 + }), 120 + }); 121 + expect(res.status).toBe(400); 122 + 123 + const body = await parseResponse(res); 124 + expect(body.success).toBe(false); 125 + }); 126 + 127 + it('returns 400 with invalid metadata.pageType', async () => { 128 + const res = await authRequest('/api/extension/import', { 129 + method: 'POST', 130 + body: JSON.stringify({ 131 + platform: 'instagram', 132 + usernames: ['testuser'], 133 + metadata: { 134 + extensionVersion: '1.0.0', 135 + scrapedAt: new Date().toISOString(), 136 + pageType: 'invalid', 137 + sourceUrl: 'https://instagram.com/testuser/following', 138 + }, 139 + }), 140 + }); 141 + expect(res.status).toBe(400); 142 + 143 + const body = await parseResponse(res); 144 + expect(body.success).toBe(false); 145 + }); 146 + 147 + it('returns 400 with invalid sourceUrl', async () => { 148 + const res = await authRequest('/api/extension/import', { 149 + method: 'POST', 150 + body: JSON.stringify({ 151 + platform: 'instagram', 152 + usernames: ['testuser'], 153 + metadata: { 154 + extensionVersion: '1.0.0', 155 + scrapedAt: new Date().toISOString(), 156 + pageType: 'following', 157 + sourceUrl: 'not-a-url', 158 + }, 159 + }), 160 + }); 161 + expect(res.status).toBe(400); 162 + 163 + const body = await parseResponse(res); 164 + expect(body.success).toBe(false); 165 + }); 166 + 167 + it('accepts valid request with single username', async () => { 168 + const res = await requestWithSession( 169 + '/api/extension/import', 170 + validSession, 171 + { 172 + method: 'POST', 173 + body: JSON.stringify({ 174 + platform: 'instagram', 175 + usernames: ['testuser'], 176 + metadata: { 177 + extensionVersion: '1.0.0', 178 + scrapedAt: new Date().toISOString(), 179 + pageType: 'following', 180 + sourceUrl: 'https://instagram.com/testuser/following', 181 + }, 182 + }), 183 + }, 184 + ); 185 + 186 + expect(res.status).toBe(200); 187 + 188 + const body = await parseResponse(res); 189 + expect(body.success).toBe(true); 190 + expect(body.data).toHaveProperty('importId'); 191 + expect(body.data).toHaveProperty('usernameCount'); 192 + expect(body.data).toHaveProperty('redirectUrl'); 193 + expect(body.data.usernameCount).toBe(1); 194 + }); 195 + 196 + it('accepts valid request with multiple usernames', async () => { 197 + const usernames = ['user1', 'user2', 'user3', 'user4', 'user5']; 198 + 199 + const res = await requestWithSession( 200 + '/api/extension/import', 201 + validSession, 202 + { 203 + method: 'POST', 204 + body: JSON.stringify({ 205 + platform: 'tiktok', 206 + usernames, 207 + metadata: { 208 + extensionVersion: '1.0.0', 209 + scrapedAt: new Date().toISOString(), 210 + pageType: 'following', 211 + sourceUrl: 'https://tiktok.com/@testuser/following', 212 + }, 213 + }), 214 + }, 215 + ); 216 + 217 + expect(res.status).toBe(200); 218 + 219 + const body = await parseResponse(res); 220 + expect(body.success).toBe(true); 221 + expect(body.data.usernameCount).toBe(5); 222 + expect(body.data.importId).toBeTruthy(); 223 + expect(body.data.redirectUrl).toContain(body.data.importId); 224 + }); 225 + 226 + it('accepts all valid pageTypes', async () => { 227 + const pageTypes = ['following', 'followers', 'list'] as const; 228 + 229 + for (const pageType of pageTypes) { 230 + const res = await requestWithSession( 231 + '/api/extension/import', 232 + validSession, 233 + { 234 + method: 'POST', 235 + body: JSON.stringify({ 236 + platform: 'twitter', 237 + usernames: ['testuser'], 238 + metadata: { 239 + extensionVersion: '1.0.0', 240 + scrapedAt: new Date().toISOString(), 241 + pageType, 242 + sourceUrl: `https://twitter.com/testuser/${pageType}`, 243 + }, 244 + }), 245 + }, 246 + ); 247 + 248 + expect(res.status).toBe(200); 249 + 250 + const body = await parseResponse(res); 251 + expect(body.success).toBe(true); 252 + } 253 + }); 254 + 255 + it('handles large username arrays (within limit)', async () => { 256 + const largeUsernameArray = Array.from({ length: 1000 }, (_, i) => `user${i}`); 257 + 258 + const res = await requestWithSession( 259 + '/api/extension/import', 260 + validSession, 261 + { 262 + method: 'POST', 263 + body: JSON.stringify({ 264 + platform: 'instagram', 265 + usernames: largeUsernameArray, 266 + metadata: { 267 + extensionVersion: '1.0.0', 268 + scrapedAt: new Date().toISOString(), 269 + pageType: 'following', 270 + sourceUrl: 'https://instagram.com/testuser/following', 271 + }, 272 + }), 273 + }, 274 + ); 275 + 276 + expect(res.status).toBe(200); 277 + 278 + const body = await parseResponse(res); 279 + expect(body.success).toBe(true); 280 + expect(body.data.usernameCount).toBe(1000); 281 + }); 282 + 283 + describe('Response Structure', () => { 284 + it('returns properly structured import response', async () => { 285 + const res = await requestWithSession( 286 + '/api/extension/import', 287 + validSession, 288 + { 289 + method: 'POST', 290 + body: JSON.stringify({ 291 + platform: 'instagram', 292 + usernames: ['testuser1', 'testuser2'], 293 + metadata: { 294 + extensionVersion: '1.0.0', 295 + scrapedAt: new Date().toISOString(), 296 + pageType: 'following', 297 + sourceUrl: 'https://instagram.com/testuser/following', 298 + }, 299 + }), 300 + }, 301 + ); 302 + 303 + expect(res.status).toBe(200); 304 + 305 + const body = await parseResponse(res); 306 + expect(body.success).toBe(true); 307 + expect(body.data).toBeDefined(); 308 + 309 + // Verify importId is a hex string 310 + expect(body.data.importId).toMatch(/^[a-f0-9]{32}$/); 311 + 312 + // Verify usernameCount matches input 313 + expect(body.data.usernameCount).toBe(2); 314 + 315 + // Verify redirectUrl contains importId 316 + expect(body.data.redirectUrl).toContain(body.data.importId); 317 + expect(body.data.redirectUrl).toMatch(/\?uploadId=/); 318 + }); 319 + }); 320 + 321 + describe('Platform Support', () => { 322 + const platforms = [ 323 + { name: 'instagram', url: 'https://instagram.com/user/following' }, 324 + { name: 'tiktok', url: 'https://tiktok.com/@user/following' }, 325 + { name: 'twitter', url: 'https://twitter.com/user/following' }, 326 + ]; 327 + 328 + platforms.forEach(({ name, url }) => { 329 + it(`accepts ${name} platform`, async () => { 330 + const res = await requestWithSession( 331 + '/api/extension/import', 332 + validSession, 333 + { 334 + method: 'POST', 335 + body: JSON.stringify({ 336 + platform: name, 337 + usernames: ['testuser'], 338 + metadata: { 339 + extensionVersion: '1.0.0', 340 + scrapedAt: new Date().toISOString(), 341 + pageType: 'following', 342 + sourceUrl: url, 343 + }, 344 + }), 345 + }, 346 + ); 347 + 348 + expect(res.status).toBe(200); 349 + 350 + const body = await parseResponse(res); 351 + expect(body.success).toBe(true); 352 + }); 353 + }); 354 + }); 355 + }); 356 + });