This repository has no description
0

Configure Feed

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

semble / src / modules / cards / tests / utils / FakeCollectionPublisher.ts
9.6 kB 307 lines
1import { ICollectionPublisher } from '../../application/ports/ICollectionPublisher'; 2import { Collection } from '../../domain/Collection'; 3import { Card } from '../../domain/Card'; 4import { PublishedRecordId } from '../../domain/value-objects/PublishedRecordId'; 5import { ok, err, Result } from '../../../../shared/core/Result'; 6import { UseCaseError } from '../../../../shared/core/UseCaseError'; 7import { AppError } from '../../../../shared/core/AppError'; 8import { CuratorId } from '../../domain/value-objects/CuratorId'; 9import { EnvironmentConfigService } from 'src/shared/infrastructure/config/EnvironmentConfigService'; 10 11export class FakeCollectionPublisher implements ICollectionPublisher { 12 private publishedCollections: Map<string, Collection> = new Map(); 13 private publishedLinks: Map< 14 string, 15 { cardId: string; linkRecord: PublishedRecordId }[] 16 > = new Map(); 17 private unpublishedCollections: Array<{ uri: string; cid: string }> = []; 18 private removedLinks: Array<{ cardId: string; collectionId: string }> = []; 19 private publishedRemovals: Map< 20 string, 21 { 22 cardId: string; 23 collectionId: string; 24 removedLink: PublishedRecordId; 25 removalRecord: PublishedRecordId; 26 }[] 27 > = new Map(); 28 private shouldFail: boolean = false; 29 private shouldFailUnpublish: boolean = false; 30 private collectionType = 31 new EnvironmentConfigService().getAtProtoCollections().collection; 32 33 private cardCollectionLinkType = 34 new EnvironmentConfigService().getAtProtoCollections().collectionLink; 35 36 private collectionLinkRemovalType = 37 new EnvironmentConfigService().getAtProtoCollections() 38 .collectionLinkRemoval; 39 40 async publish( 41 collection: Collection, 42 ): Promise<Result<PublishedRecordId, UseCaseError>> { 43 if (this.shouldFail) { 44 return err( 45 AppError.UnexpectedError.create( 46 new Error('Simulated collection publish failure'), 47 ), 48 ); 49 } 50 51 const collectionId = collection.collectionId.getStringValue(); 52 53 // Use the collection's author DID directly 54 const fakeDid = collection.authorId.value; 55 56 // Simulate publishing the collection record itself 57 const fakeCollectionUri = `at://${fakeDid}/${this.collectionType}/${collectionId}`; 58 const fakeCollectionCid = `fake-collection-cid-${collectionId}`; 59 60 const collectionRecord = PublishedRecordId.create({ 61 uri: fakeCollectionUri, 62 cid: fakeCollectionCid, 63 }); 64 65 // Store the published collection for inspection 66 this.publishedCollections.set(collectionId, collection); 67 68 console.log( 69 `[FakeCollectionPublisher] Published collection ${collectionId}`, 70 ); 71 72 return ok(collectionRecord); 73 } 74 75 async publishCardAddedToCollection( 76 card: Card, 77 collection: Collection, 78 curatorId: CuratorId, 79 ): Promise<Result<PublishedRecordId, UseCaseError>> { 80 if (this.shouldFail) { 81 return err( 82 AppError.UnexpectedError.create( 83 new Error('Simulated card-collection link publish failure'), 84 ), 85 ); 86 } 87 88 const collectionId = collection.collectionId.getStringValue(); 89 const cardId = card.cardId.getStringValue(); 90 91 // Simulate publishing a card-collection link 92 const fakeLinkUri = `at://${curatorId.value}/${this.cardCollectionLinkType}/${collectionId}-${cardId}`; 93 const fakeLinkCid = `fake-link-cid-${collectionId}-${cardId}`; 94 95 const linkRecord = PublishedRecordId.create({ 96 uri: fakeLinkUri, 97 cid: fakeLinkCid, 98 }); 99 100 // Store published link for inspection 101 const existingLinks = this.publishedLinks.get(collectionId) || []; 102 existingLinks.push({ 103 cardId, 104 linkRecord, 105 }); 106 this.publishedLinks.set(collectionId, existingLinks); 107 108 console.log( 109 `[FakeCollectionPublisher] Published card ${cardId} added to collection ${collectionId} link at ${fakeLinkUri}`, 110 ); 111 112 return ok(linkRecord); 113 } 114 115 async unpublishCardAddedToCollection( 116 recordId: PublishedRecordId, 117 ): Promise<Result<void, UseCaseError>> { 118 if (this.shouldFailUnpublish) { 119 return err( 120 AppError.UnexpectedError.create( 121 new Error('Simulated card-collection link unpublish failure'), 122 ), 123 ); 124 } 125 126 // Find and remove the link by its published record ID 127 for (const [collectionId, links] of this.publishedLinks.entries()) { 128 const linkIndex = links.findIndex( 129 (link) => link.linkRecord.uri === recordId.uri, 130 ); 131 if (linkIndex !== -1) { 132 const removedLink = links.splice(linkIndex, 1)[0]; 133 this.removedLinks.push({ 134 cardId: removedLink!.cardId, 135 collectionId, 136 }); 137 console.log( 138 `[FakeCollectionPublisher] Unpublished card-collection link ${recordId.uri}`, 139 ); 140 return ok(undefined); 141 } 142 } 143 144 console.warn( 145 `[FakeCollectionPublisher] Card-collection link not found for unpublishing: ${recordId.uri}`, 146 ); 147 return ok(undefined); 148 } 149 150 async publishCollectionLinkRemoval( 151 card: Card, 152 collection: Collection, 153 curatorId: CuratorId, 154 removedLinkRef: PublishedRecordId, 155 ): Promise<Result<PublishedRecordId, UseCaseError>> { 156 if (this.shouldFail) { 157 return err( 158 AppError.UnexpectedError.create( 159 new Error('Simulated collection link removal publish failure'), 160 ), 161 ); 162 } 163 164 const collectionId = collection.collectionId.getStringValue(); 165 const cardId = card.cardId.getStringValue(); 166 167 // Simulate publishing a collection link removal record 168 const fakeRemovalUri = `at://${curatorId.value}/${this.collectionLinkRemovalType}/${collectionId}-${cardId}-removal`; 169 const fakeRemovalCid = `fake-removal-cid-${collectionId}-${cardId}`; 170 171 const removalRecord = PublishedRecordId.create({ 172 uri: fakeRemovalUri, 173 cid: fakeRemovalCid, 174 }); 175 176 // Store published removal for inspection 177 const existingRemovals = this.publishedRemovals.get(collectionId) || []; 178 existingRemovals.push({ 179 cardId, 180 collectionId, 181 removedLink: removedLinkRef, 182 removalRecord, 183 }); 184 this.publishedRemovals.set(collectionId, existingRemovals); 185 186 console.log( 187 `[FakeCollectionPublisher] Published removal of card ${cardId} from collection ${collectionId} at ${fakeRemovalUri}`, 188 ); 189 190 return ok(removalRecord); 191 } 192 193 async unpublish( 194 recordId: PublishedRecordId, 195 ): Promise<Result<void, UseCaseError>> { 196 if (this.shouldFailUnpublish) { 197 return err( 198 AppError.UnexpectedError.create( 199 new Error('Simulated collection unpublish failure'), 200 ), 201 ); 202 } 203 204 // Find and remove the collection by its published record ID 205 for (const [ 206 collectionId, 207 collection, 208 ] of this.publishedCollections.entries()) { 209 if (collection.publishedRecordId?.uri === recordId.uri) { 210 this.publishedCollections.delete(collectionId); 211 this.publishedLinks.delete(collectionId); 212 this.unpublishedCollections.push({ 213 uri: recordId.uri, 214 cid: recordId.cid, 215 }); 216 console.log( 217 `[FakeCollectionPublisher] Unpublished collection ${recordId.uri}`, 218 ); 219 return ok(undefined); 220 } 221 } 222 if (this.publishedCollections.size === 0) { 223 this.unpublishedCollections.push({ 224 uri: recordId.uri, 225 cid: recordId.cid, 226 }); 227 console.log( 228 `[FakeCollectionPublisher] Unpublished collection ${recordId.uri} (not found in published collections)`, 229 ); 230 return ok(undefined); 231 } 232 233 console.warn( 234 `[FakeCollectionPublisher] Collection not found for unpublishing: ${recordId.uri}`, 235 ); 236 return ok(undefined); 237 } 238 239 setShouldFail(shouldFail: boolean): void { 240 this.shouldFail = shouldFail; 241 } 242 243 setShouldFailUnpublish(shouldFailUnpublish: boolean): void { 244 this.shouldFailUnpublish = shouldFailUnpublish; 245 } 246 247 clear(): void { 248 this.publishedCollections.clear(); 249 this.publishedLinks.clear(); 250 this.unpublishedCollections = []; 251 this.removedLinks = []; 252 this.publishedRemovals.clear(); 253 this.shouldFail = false; 254 this.shouldFailUnpublish = false; 255 } 256 257 getPublishedCollections(): Collection[] { 258 return Array.from(this.publishedCollections.values()); 259 } 260 261 getPublishedLinksForCollection( 262 collectionId: string, 263 ): Array<{ cardId: string; linkRecord: PublishedRecordId }> { 264 return this.publishedLinks.get(collectionId) || []; 265 } 266 267 getUnpublishedCollections(): Array<{ uri: string; cid: string }> { 268 return this.unpublishedCollections; 269 } 270 271 getRemovedLinksForCollection( 272 collectionId: string, 273 ): Array<{ cardId: string; collectionId: string }> { 274 return this.removedLinks.filter( 275 (link) => link.collectionId === collectionId, 276 ); 277 } 278 279 getAllRemovedLinks(): Array<{ cardId: string; collectionId: string }> { 280 return this.removedLinks; 281 } 282 getAllPublishedLinks() { 283 const allLinks: Array<{ cardId: string; linkRecord: PublishedRecordId }> = 284 []; 285 for (const links of this.publishedLinks.values()) { 286 allLinks.push(...links); 287 } 288 return allLinks; 289 } 290 291 getPublishedRemovalsForCollection(collectionId: string) { 292 return this.publishedRemovals.get(collectionId) || []; 293 } 294 295 getAllPublishedRemovals() { 296 const allRemovals: Array<{ 297 cardId: string; 298 collectionId: string; 299 removedLink: PublishedRecordId; 300 removalRecord: PublishedRecordId; 301 }> = []; 302 for (const removals of this.publishedRemovals.values()) { 303 allRemovals.push(...removals); 304 } 305 return allRemovals; 306 } 307}