Import Instagram archive to a Bluesky account
9
fork

Configure Feed

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

Refactor test suite and update TypeScript configuration

- Update import paths in test files to use relative imports
- Modify test mocks to use new InstagramMediaProcessor
- Update tsconfig.jest.json to include ES2023 library
- Simplify test setup and mock implementations
- Remove unnecessary mocked modules and dependencies

+89 -57
+3 -9
src/bluesky/bluesky.ts
··· 1 1 import { 2 2 AtpAgent, 3 3 RichText, 4 - BlobRef, 5 - AppBskyEmbedVideo, 6 - AppBskyEmbedImages, 4 + BlobRef 7 5 } from "@atproto/api"; 8 6 9 - import { logger } from "@logger/logger.js"; 7 + import { logger } from "@logger/logger"; 10 8 11 9 import { 12 - ImageEmbed, 13 10 EmbeddedMedia, 14 - ImageEmbedImpl, 15 - VideoEmbedImpl, 16 - ImagesEmbedImpl, 17 11 PostRecordImpl 18 - } from "./types/index.js"; 12 + } from "./types"; 19 13 20 14 21 15 export class BlueskyClient {
+2 -2
src/bluesky/index.ts
··· 1 - export * from './types/index.js'; 2 - export * from './bluesky.js'; 1 + export * from './types/index'; 2 + export * from './bluesky';
+5 -5
src/bluesky/types/index.ts
··· 1 - export * from "./ImageEmbed.js"; 2 - export * from "./VideoEmbed.js"; 3 - export * from "./ImagesEmbed.js"; 4 - export * from "./PostRecord.js"; 5 - export * from "./EmbeddedMedia.js"; 1 + export * from "./ImageEmbed"; 2 + export * from "./VideoEmbed"; 3 + export * from "./ImagesEmbed"; 4 + export * from "./PostRecord"; 5 + export * from "./EmbeddedMedia";
+76 -39
src/instagram-to-bluesky.test.ts
··· 1 1 import fs from 'fs'; 2 2 3 - import { main, formatDuration, calculateEstimatedTime } from '@src/instagram-to-bluesky.js'; 4 - import { BlueskyClient } from '@src/bluesky/bluesky.js'; 5 - import { logger } from '@src/logger/logger.js'; 6 - import { processPost } from '@src/media/index.js'; 3 + import { main, formatDuration, calculateEstimatedTime } from '../src/instagram-to-bluesky'; 4 + import { BlueskyClient } from '../src/bluesky/bluesky'; 5 + import { logger } from '../src/logger/logger'; 6 + import { InstagramMediaProcessor } from '../src/media/media'; 7 + import type { InstagramExportedPost } from '../src/media/InstagramExportedPost'; 7 8 8 9 // Mock all dependencies 9 10 jest.mock('fs'); 10 - jest.mock('../src/bluesky'); 11 - jest.mock('../src/media'); 12 - jest.mock('../src/logger', () => ({ 11 + jest.mock('../src/bluesky/bluesky', () => { 12 + return { 13 + BlueskyClient: jest.fn().mockImplementation(() => ({ 14 + login: jest.fn().mockResolvedValue(undefined), 15 + uploadMedia: jest.fn().mockResolvedValue({ 16 + ref: 'test-blob-ref', 17 + mimeType: 'image/jpeg', 18 + size: 1000 19 + }), 20 + createPost: jest.fn().mockResolvedValue('https://bsky.app/profile/test/post/test') 21 + })) 22 + }; 23 + }); 24 + jest.mock('../src/media/media', () => { 25 + const mockProcess = jest.fn().mockResolvedValue([{ 26 + postDate: new Date(), 27 + postText: 'Test post', 28 + embeddedMedia: [], 29 + mediaCount: 1 30 + }]); 31 + 32 + const mockMediaProcessor = { 33 + process: jest.fn().mockResolvedValue([{ 34 + mediaText: 'Test media', 35 + mimeType: 'image/jpeg', 36 + mediaBuffer: Buffer.from('test') 37 + }]) 38 + }; 39 + 40 + return { 41 + InstagramMediaProcessor: jest.fn().mockImplementation( 42 + (posts: InstagramExportedPost[], folder: string) => ({ 43 + mediaProcessorFactory: { 44 + createProcessor: () => mockMediaProcessor 45 + }, 46 + instagramPosts: posts, 47 + archiveFolder: folder, 48 + process: mockProcess 49 + }) 50 + ), 51 + decodeUTF8: jest.fn(x => x) 52 + }; 53 + }); 54 + jest.mock('../src/logger/logger', () => ({ 13 55 logger: { 14 56 info: jest.fn(), 15 57 warn: jest.fn(), ··· 36 78 processVideoPost: jest.fn() 37 79 })); 38 80 39 - // Add this mock before the tests 40 - jest.mock('../src/app', () => { 41 - const originalModule = jest.requireActual('../src/app'); 42 - return { 43 - ...originalModule, 44 - getArchiveFolder: () => '/test/folder' 45 - }; 46 - }); 47 - 48 81 describe('Main App', () => { 49 82 const originalEnv = process.env; 50 83 ··· 71 104 }] 72 105 }])); 73 106 74 - (processPost as jest.Mock).mockResolvedValue({ 75 - postDate: new Date(), 76 - postText: 'Test post', 77 - embeddedMedia: [], 78 - mediaCount: 1 79 - }); 80 - 81 107 // Reset BlueskyClient mock 82 108 jest.mocked(BlueskyClient).mockClear(); 83 109 jest.mocked(BlueskyClient).prototype.login = jest.fn(); ··· 114 140 await main(); 115 141 116 142 expect(jest.mocked(BlueskyClient)).toHaveBeenCalled(); 117 - expect(processPost).toHaveBeenCalledWith( 118 - expect.objectContaining(mockPost), 119 - expect.stringContaining('/test/folder'), 120 - expect.any(BlueskyClient), 121 - false 143 + expect(InstagramMediaProcessor).toHaveBeenCalledWith( 144 + expect.any(Array), 145 + expect.stringContaining('/test/folder') 122 146 ); 147 + expect(jest.mocked(InstagramMediaProcessor).mock.results[0].value.process).toHaveBeenCalled(); 123 148 }); 124 149 125 150 test('should handle date filtering with MIN_DATE', async () => { ··· 217 242 218 243 (fs.readFileSync as jest.Mock).mockReturnValue(JSON.stringify([mockPost])); 219 244 220 - (processPost as jest.Mock).mockResolvedValue({ 221 - postDate: new Date(), 222 - postText: 'Test post', 223 - embeddedMedia: [], 224 - mediaCount: 10 // 10 media items 225 - }); 245 + jest.mocked(InstagramMediaProcessor).mockImplementation(() => ({ 246 + mediaProcessorFactory: { 247 + createProcessor: () => ({ 248 + process: jest.fn().mockResolvedValue([{ 249 + mediaText: 'Test media', 250 + mimeType: 'image/jpeg', 251 + mediaBuffer: Buffer.from('test') 252 + }]) 253 + }) 254 + }, 255 + instagramPosts: [], 256 + archiveFolder: '', 257 + process: jest.fn().mockResolvedValue([{ 258 + postDate: new Date(), 259 + postText: 'Test post', 260 + embeddedMedia: [], 261 + mediaCount: 10 // 10 media items 262 + }]) 263 + })); 226 264 227 265 await main(); 228 266 ··· 245 283 await main(); 246 284 247 285 expect(jest.mocked(BlueskyClient)).toHaveBeenCalled(); 248 - expect(processPost).toHaveBeenCalledWith( 249 - expect.objectContaining(mockPost), 250 - expect.stringContaining('/test/folder'), 251 - expect.any(BlueskyClient), 252 - false 286 + expect(InstagramMediaProcessor).toHaveBeenCalledWith( 287 + expect.any(Array), 288 + expect.stringContaining('/test/folder') 253 289 ); 290 + expect(jest.mocked(InstagramMediaProcessor).mock.results[0].value.process).toHaveBeenCalled(); 254 291 }); 255 292 }); 256 293
+1 -1
src/instagram-to-bluesky.ts
··· 138 138 139 139 // Sort instagram posts by creation timestamp 140 140 if (instaPosts && instaPosts.length > 0) { 141 - const sortedPosts = instaPosts.toSorted((a, b) => { 141 + const sortedPosts = instaPosts.sort((a, b) => { 142 142 // Get the first posts media and compare timestamps. 143 143 const ad = a.media[0].creation_timestamp; 144 144 const bd = b.media[0].creation_timestamp;
+2 -1
tsconfig.jest.json
··· 3 3 "compilerOptions": { 4 4 "types": ["jest", "node"], 5 5 "isolatedModules": true, 6 - "esModuleInterop": true 6 + "esModuleInterop": true, 7 + "lib": ["ES2023"] 7 8 }, 8 9 "include": ["src/**/*.test.ts"], 9 10 "exclude": ["node_modules"]