A social knowledge tool for researchers built on ATProto
at main 211 lines 7.5 kB view raw
1import { 2 PostgreSqlContainer, 3 StartedPostgreSqlContainer, 4} from '@testcontainers/postgresql'; 5import postgres from 'postgres'; 6import { drizzle, PostgresJsDatabase } from 'drizzle-orm/postgres-js'; 7import { DrizzleCardQueryRepository } from '../../infrastructure/repositories/DrizzleCardQueryRepository'; 8import { DrizzleCardRepository } from '../../infrastructure/repositories/DrizzleCardRepository'; 9import { CuratorId } from '../../domain/value-objects/CuratorId'; 10import { UniqueEntityID } from '../../../../shared/domain/UniqueEntityID'; 11import { cards } from '../../infrastructure/repositories/schema/card.sql'; 12import { libraryMemberships } from '../../infrastructure/repositories/schema/libraryMembership.sql'; 13import { publishedRecords } from '../../infrastructure/repositories/schema/publishedRecord.sql'; 14import { CardBuilder } from '../utils/builders/CardBuilder'; 15import { URL } from '../../domain/value-objects/URL'; 16import { UrlMetadata } from '../../domain/value-objects/UrlMetadata'; 17import { createTestSchema } from '../test-utils/createTestSchema'; 18import { CardTypeEnum } from '../../domain/value-objects/CardType'; 19 20describe('DrizzleCardQueryRepository - getUrlCardBasic', () => { 21 let container: StartedPostgreSqlContainer; 22 let db: PostgresJsDatabase; 23 let queryRepository: DrizzleCardQueryRepository; 24 let cardRepository: DrizzleCardRepository; 25 26 // Test data 27 let curatorId: CuratorId; 28 let otherCuratorId: CuratorId; 29 30 // Setup before all tests 31 beforeAll(async () => { 32 // Start PostgreSQL container 33 container = await new PostgreSqlContainer('postgres:14').start(); 34 35 // Create database connection 36 const connectionString = container.getConnectionUri(); 37 process.env.DATABASE_URL = connectionString; 38 const client = postgres(connectionString); 39 db = drizzle(client); 40 41 // Create repositories 42 queryRepository = new DrizzleCardQueryRepository(db); 43 cardRepository = new DrizzleCardRepository(db); 44 45 // Create schema using helper function 46 await createTestSchema(db); 47 48 // Create test data 49 curatorId = CuratorId.create('did:plc:testcurator').unwrap(); 50 otherCuratorId = CuratorId.create('did:plc:othercurator').unwrap(); 51 }, 60000); // Increase timeout for container startup 52 53 // Cleanup after all tests 54 afterAll(async () => { 55 // Stop container 56 await container.stop(); 57 }); 58 59 // Clear data between tests 60 beforeEach(async () => { 61 await db.delete(libraryMemberships); 62 await db.delete(cards); 63 await db.delete(publishedRecords); 64 }); 65 66 describe('getUrlCardBasic', () => { 67 it('should return null when card does not exist', async () => { 68 const nonExistentCardId = new UniqueEntityID().toString(); 69 70 const result = await queryRepository.getUrlCardBasic(nonExistentCardId); 71 72 expect(result).toBeNull(); 73 }); 74 75 it('should return null when card exists but is not a URL card', async () => { 76 // Create a note card 77 const noteCard = new CardBuilder() 78 .withCuratorId(curatorId.value) 79 .withNoteCard('This is a note') 80 .buildOrThrow(); 81 82 await cardRepository.save(noteCard); 83 84 const result = await queryRepository.getUrlCardBasic( 85 noteCard.cardId.getStringValue(), 86 ); 87 88 expect(result).toBeNull(); 89 }); 90 91 it('should return URL card with basic metadata', async () => { 92 // Create URL card with metadata 93 const url = URL.create('https://example.com/article').unwrap(); 94 const urlMetadata = UrlMetadata.create({ 95 url: url.value, 96 title: 'Test Article', 97 description: 'A test article description', 98 author: 'John Doe', 99 imageUrl: 'https://example.com/image.jpg', 100 }).unwrap(); 101 102 const urlCard = new CardBuilder() 103 .withCuratorId(curatorId.value) 104 .withUrlCard(url, urlMetadata) 105 .buildOrThrow(); 106 107 await cardRepository.save(urlCard); 108 109 const result = await queryRepository.getUrlCardBasic( 110 urlCard.cardId.getStringValue(), 111 ); 112 113 expect(result).toBeDefined(); 114 expect(result?.id).toBe(urlCard.cardId.getStringValue()); 115 expect(result?.type).toBe(CardTypeEnum.URL); 116 expect(result?.url).toBe(url.value); 117 expect(result?.cardContent.title).toBe('Test Article'); 118 expect(result?.cardContent.description).toBe( 119 'A test article description', 120 ); 121 expect(result?.cardContent.author).toBe('John Doe'); 122 expect(result?.cardContent.thumbnailUrl).toBe( 123 'https://example.com/image.jpg', 124 ); 125 expect(result?.note).toBeUndefined(); 126 }); 127 128 it('should include connected note card by the same author', async () => { 129 // Create URL card 130 const url = URL.create('https://example.com/article-with-note').unwrap(); 131 const urlCard = new CardBuilder() 132 .withCuratorId(curatorId.value) 133 .withUrlCard(url) 134 .buildOrThrow(); 135 136 await cardRepository.save(urlCard); 137 138 // Create connected note card by the same author 139 const noteCard = new CardBuilder() 140 .withCuratorId(curatorId.value) 141 .withNoteCard('This is my note about the article') 142 .withParentCard(urlCard.cardId) 143 .buildOrThrow(); 144 145 await cardRepository.save(noteCard); 146 147 const result = await queryRepository.getUrlCardBasic( 148 urlCard.cardId.getStringValue(), 149 ); 150 151 expect(result).toBeDefined(); 152 expect(result?.note).toBeDefined(); 153 expect(result?.note?.id).toBe(noteCard.cardId.getStringValue()); 154 expect(result?.note?.text).toBe('This is my note about the article'); 155 }); 156 157 it('should NOT include note card by a different author', async () => { 158 // Create URL card by first author 159 const url = URL.create( 160 'https://example.com/article-different-author', 161 ).unwrap(); 162 const urlCard = new CardBuilder() 163 .withCuratorId(curatorId.value) 164 .withUrlCard(url) 165 .buildOrThrow(); 166 167 await cardRepository.save(urlCard); 168 169 // Create connected note card by a DIFFERENT author 170 const noteCard = new CardBuilder() 171 .withCuratorId(otherCuratorId.value) // Different author 172 .withNoteCard('This is someone elses note') 173 .withParentCard(urlCard.cardId) 174 .buildOrThrow(); 175 176 await cardRepository.save(noteCard); 177 178 const result = await queryRepository.getUrlCardBasic( 179 urlCard.cardId.getStringValue(), 180 ); 181 182 expect(result).toBeDefined(); 183 expect(result?.note).toBeUndefined(); // Should not include note from different author 184 }); 185 186 it('should handle URL card without metadata', async () => { 187 // Create URL card without metadata 188 const url = URL.create('https://example.com/minimal').unwrap(); 189 const urlCard = new CardBuilder() 190 .withCuratorId(curatorId.value) 191 .withUrlCard(url) 192 .buildOrThrow(); 193 194 await cardRepository.save(urlCard); 195 196 const result = await queryRepository.getUrlCardBasic( 197 urlCard.cardId.getStringValue(), 198 ); 199 200 expect(result).toBeDefined(); 201 expect(result?.id).toBe(urlCard.cardId.getStringValue()); 202 expect(result?.type).toBe(CardTypeEnum.URL); 203 expect(result?.url).toBe(url.value); 204 expect(result?.cardContent.title).toBeUndefined(); 205 expect(result?.cardContent.description).toBeUndefined(); 206 expect(result?.cardContent.author).toBeUndefined(); 207 expect(result?.cardContent.thumbnailUrl).toBeUndefined(); 208 expect(result?.note).toBeUndefined(); 209 }); 210 }); 211});