Import Instagram archive to a Bluesky account
9
fork

Configure Feed

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

Refactor Bluesky client tests and media upload logic

- Update test suite to use unified uploadMedia method for images and videos
- Simplify mocking of AtpAgent and blob upload
- Remove deprecated video-specific upload tests
- Fix import paths and update test coverage
- Improve error handling for media uploads

+32 -67
+31 -66
src/bluesky/bluesky.test.ts
··· 4 4 5 5 import fs from 'fs'; 6 6 7 - import { BlueskyClient } from './bluesky.js'; 7 + import { BlueskyClient } from './bluesky'; 8 8 9 - import { ImagesEmbedImpl, VideoEmbedImpl } from './types/index.js'; 9 + import { ImagesEmbedImpl, VideoEmbedImpl } from './types/index'; 10 10 11 11 const TEST_VIDEO_PATH = './transfer/test_videos/AQM8KYlOYHTF5GlP43eMroHUpmnFHJh5CnCJUdRUeqWxG4tNX7D43eM77F152vfi4znTzgkFTTzzM4nHa_v8ugmP4WPRJtjKPZX5pko_17845940218109367.mp4'; 12 - 12 + const TEST_IMAGE_PATH = './transfer/test_images/454093999_1240420453752667_4632794683080840290_n_18047065138879510.jpg' 13 13 jest.mock('@atproto/api', () => ({ 14 14 AtpAgent: jest.fn().mockImplementation(() => ({ 15 15 login: jest.fn(), 16 16 post: jest.fn().mockResolvedValue({ uri: 'at://did:plc:test/app.bsky.feed.post/123' }), 17 - uploadBlob: jest.fn().mockResolvedValue({ 18 - data: { blob: { $type: 'blob', ref: { $link: 'test-blob-ref' } } } 19 - }), 20 - api: { 21 - app: { 22 - bsky: { 23 - video: { 24 - uploadVideo: jest.fn().mockResolvedValue({ 25 - data: { 26 - jobStatus: { 27 - jobId: 'test-job-id' 28 - } 29 - } 30 - }), 31 - getJobStatus: jest.fn().mockResolvedValue({ 32 - data: { 33 - jobStatus: { 34 - state: 'JOB_STATE_COMPLETED', 35 - blob: { 36 - $type: 'blob', 37 - ref: { $link: 'test-blob-ref' }, 38 - mimeType: 'video/mp4', 39 - size: 1000 40 - } 41 - } 42 - } 43 - }) 44 - } 45 - } 46 - } 47 - } 17 + uploadBlob: jest.fn() 48 18 })), 49 19 RichText: jest.fn().mockImplementation(() => ({ 50 20 detectFacets: jest.fn(), 51 21 text: 'test text', 52 22 facets: [] 23 + })), 24 + BlobRef: jest.fn().mockImplementation((ref, mimeType, size) => ({ 25 + ref, 26 + mimeType, 27 + size 53 28 })) 54 29 })); 55 30 56 31 describe('BlueskyClient', () => { 57 32 let client: BlueskyClient; 58 - // let mockCID: CID; 59 33 let videoBuffer: Buffer; 34 + let imageBuffer: Buffer; 35 + let mockAgent: any; 60 36 61 37 beforeEach(() => { 62 38 client = new BlueskyClient('test-user', 'test-pass'); 39 + mockAgent = jest.requireMock('@atproto/api').AtpAgent.mock.results[0].value; 40 + mockAgent.uploadBlob.mockResolvedValue({ 41 + data: { blob: { $type: 'blob', ref: 'test-blob-ref' } } 42 + }); 63 43 }); 64 44 65 45 beforeAll(async () => { 66 46 videoBuffer = fs.readFileSync(TEST_VIDEO_PATH); 47 + imageBuffer = fs.readFileSync(TEST_IMAGE_PATH); 67 48 /** 68 49 * CID from test video uploaded to Pinata.cloud. 69 50 * Creating CID from the video proved to be too challenging. ··· 81 62 expect(postUrl).toContain('https://bsky.app/profile/test-user/post/'); 82 63 }); 83 64 84 - xtest('should upload video successfully', async () => { 85 - const buffer = Buffer.from('test video'); 86 - const blob = await client.uploadVideo(buffer, 'video/mp4'); 65 + test('should upload image successfully', async () => { 66 + const blob = await client.uploadMedia(imageBuffer, 'image/jpeg'); 87 67 88 68 expect(blob).toBeDefined(); 89 69 expect(blob.ref).toBe('test-blob-ref'); 90 70 }); 91 71 92 - xtest('should handle video upload failure', async () => { 93 - const mockAgent = jest.requireMock('@atproto/api').AtpAgent.mock.results[0].value; 94 - mockAgent.api.app.bsky.video.getJobStatus.mockResolvedValueOnce({ 95 - data: { 96 - jobStatus: { 97 - state: 'JOB_STATE_FAILED', 98 - error: 'Test error' 99 - } 100 - } 101 - }); 72 + test('should upload video successfully', async () => { 73 + const blob = await client.uploadMedia(videoBuffer, 'video/mp4'); 102 74 103 - const buffer = Buffer.from('test video'); 104 - await expect(client.uploadVideo(buffer, 'video/mp4')).rejects.toThrow('Video upload failed: Test error'); 75 + expect(blob).toBeDefined(); 76 + expect(blob.ref).toBe('test-blob-ref'); 105 77 }); 106 78 107 - xtest('should create video post successfully', async () => { 79 + test('should handle media upload failure', async () => { 80 + mockAgent.uploadBlob.mockRejectedValueOnce(new Error('Upload failed')); 81 + await expect(client.uploadMedia(imageBuffer, 'image/jpeg')).rejects.toThrow('Upload failed'); 82 + }); 83 + 84 + test('should create video post successfully', async () => { 85 + const blob = await client.uploadMedia(videoBuffer, 'video/mp4'); 86 + 108 87 const videoEmbed = new VideoEmbedImpl( 109 88 'test video', 110 89 videoBuffer, 111 90 'video/mp4', 112 91 1000, 113 - new BlobRef({} as unknown as any, 'video/mp4', 1000) 92 + blob 114 93 ); 115 94 116 95 const postUrl = await client.createPost( ··· 120 99 ); 121 100 122 101 expect(postUrl).toContain('https://bsky.app/profile/test-user/post/123'); 123 - }); 124 - 125 - xtest('should handle video upload timeout', async () => { 126 - const mockAgent = jest.requireMock('@atproto/api').AtpAgent.mock.results[0].value; 127 - mockAgent.api.app.bsky.video.getJobStatus.mockResolvedValue({ 128 - data: { 129 - jobStatus: { 130 - state: 'JOB_STATE_PROCESSING' 131 - } 132 - } 133 - }); 134 - 135 - const buffer = Buffer.from('test video'); 136 - await expect(client.uploadVideo(buffer, 'video/mp4')).rejects.toThrow('Video upload timed out'); 137 102 }); 138 103 });
+1 -1
src/bluesky/bluesky.ts
··· 4 4 BlobRef 5 5 } from "@atproto/api"; 6 6 7 - import { logger } from "@logger/logger"; 7 + import { logger } from "../logger/logger"; 8 8 9 9 import { 10 10 EmbeddedMedia,