diff --git a/apps/api/src/shops/shop.service.spec.ts b/apps/api/src/shops/shop.service.spec.ts index 520ff9e..0586d6b 100644 --- a/apps/api/src/shops/shop.service.spec.ts +++ b/apps/api/src/shops/shop.service.spec.ts @@ -491,10 +491,27 @@ describe('ShopService', () => { effectSummary: 'Capacity: 5 Raider Trophies', unlocked: true, }); + // The capacity line is sent once, as the effect. Repeating it as the + // description makes the row render it twice (slice §5). + expect(view.offers[0].itemDescription).toBe(''); }); it('grants a bag rather than stacking it as an item', async () => { - const world = createWorld({ bagOffer: true, silver: 100 }); + // Slice §10 case 3: the reputation gate is present *and* satisfied, so the + // purchase goes through. A fixture with no conditions at all would pass + // without the gate ever being consulted. + const world = createWorld({ + bagOffer: true, + silver: 100, + conditions: [ + { + type: GameConditionType.REGION_REPUTATION, + key: 'border-guard', + operator: ComparisonOperator.GTE, + value: 25, + }, + ], + }); const result = await world.service.purchase( CHARACTER_ID, @@ -615,6 +632,36 @@ describe('ShopService', () => { expect(world.grantedItems).toHaveLength(0); }); + it('blocks a renown-gated offer without blaming merchant reputation', async () => { + // Slice §10 case 4, and the discrimination this branch introduced: World + // Renown is not the merchant's regard, so a renown block must read as a + // plain lock. Sending this player off to trade pelts with Borin would be + // pointing at the wrong bar entirely. + const world = createWorld({ + silver: 1000, + unmetConditionTypes: [GameConditionType.WORLD_RENOWN], + conditions: [ + { + type: GameConditionType.WORLD_RENOWN, + operator: ComparisonOperator.GTE, + value: 3, + }, + ], + }); + + await expect( + world.service.purchase( + CHARACTER_ID, + MERCHANT_KEY, + 'small-healing-potion', + 1, + ), + ).rejects.toMatchObject({ code: 'SHOP_OFFER_LOCKED' }); + + expect(world.character.silver).toBe(1000); + expect(world.grantedItems).toHaveLength(0); + }); + it('opens an offer whose bypass holds even though its conditions do not', async () => { // The Slice 0.9 referral: the warden's word is worth more than the // reputation the player has not earned yet (slice §7). @@ -653,8 +700,21 @@ describe('ShopService', () => { }); it('still charges the price when a requirement is met', async () => { - // Reputation opens the offer; it does not pay for it (slice §10). - const world = createWorld({ bagOffer: true, silver: 10 }); + // Reputation opens the offer; it does not pay for it (slice §10). The + // requirement has to actually exist and hold for that to be the thing + // under test. + const world = createWorld({ + bagOffer: true, + silver: 10, + conditions: [ + { + type: GameConditionType.REGION_REPUTATION, + key: 'border-guard', + operator: ComparisonOperator.GTE, + value: 25, + }, + ], + }); await expect( world.service.purchase(