feat(release): prepare 1.1.0 candidate
This commit is contained in:
parent
5a34f66981
commit
5205dcdfa9
736 changed files with 115667 additions and 12203 deletions
152
apps/mobile-rn/__tests__/billing-restore-provider.test.tsx
Normal file
152
apps/mobile-rn/__tests__/billing-restore-provider.test.tsx
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
import React from 'react'
|
||||
import ReactTestRenderer, { act } from 'react-test-renderer'
|
||||
import { Platform } from 'react-native'
|
||||
|
||||
jest.mock('../src/lib/auth-context', () => ({ useAuth: jest.fn() }))
|
||||
jest.mock('../src/lib/entitlement-context', () => ({ useEntitlement: jest.fn() }))
|
||||
jest.mock('../src/lib/native-config', () => ({ getMobileRuntimeConfig: jest.fn() }))
|
||||
jest.mock('../src/lib/supabase', () => ({
|
||||
supabase: { functions: { invoke: jest.fn() } },
|
||||
}))
|
||||
|
||||
const mockedAuth = jest.requireMock('../src/lib/auth-context') as { useAuth: jest.Mock }
|
||||
const mockedEntitlement = jest.requireMock('../src/lib/entitlement-context') as {
|
||||
useEntitlement: jest.Mock
|
||||
}
|
||||
const mockedConfig = jest.requireMock('../src/lib/native-config') as {
|
||||
getMobileRuntimeConfig: jest.Mock
|
||||
}
|
||||
const mockedSupabase = jest.requireMock('../src/lib/supabase').supabase as {
|
||||
functions: { invoke: jest.Mock }
|
||||
}
|
||||
const mockedIap = jest.requireActual('react-native-iap') as {
|
||||
getAvailablePurchases: jest.Mock
|
||||
useIAP: jest.Mock
|
||||
__stableUseIapResult: Record<string, unknown> & { restorePurchases: jest.Mock }
|
||||
}
|
||||
|
||||
import { BillingProvider, useBilling } from '../src/lib/billing-context'
|
||||
|
||||
const USER_ID = '11111111-1111-4111-8111-111111111111'
|
||||
const PURCHASE_ID = '22222222-2222-4222-8222-222222222222'
|
||||
const PRODUCT_ID = 'd3ro_voice_pro_monthly'
|
||||
const TOKEN = `play_${'a'.repeat(32)}`
|
||||
const originalPlatform = Platform.OS
|
||||
let latestBilling: ReturnType<typeof useBilling> | null = null
|
||||
|
||||
function Probe(): null {
|
||||
latestBilling = useBilling()
|
||||
return null
|
||||
}
|
||||
|
||||
describe('BillingProvider explicit restore flow', () => {
|
||||
beforeAll(() => {
|
||||
Object.defineProperty(Platform, 'OS', { configurable: true, value: 'android' })
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
Object.defineProperty(Platform, 'OS', { configurable: true, value: originalPlatform })
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
latestBilling = null
|
||||
mockedAuth.useAuth.mockReturnValue({ user: { id: USER_ID } })
|
||||
mockedConfig.getMobileRuntimeConfig.mockReturnValue({ installedFromPlayStore: true })
|
||||
mockedIap.__stableUseIapResult.restorePurchases.mockClear()
|
||||
mockedIap.__stableUseIapResult.connected = true
|
||||
mockedIap.__stableUseIapResult.subscriptions = []
|
||||
mockedIap.useIAP.mockImplementation(() => mockedIap.__stableUseIapResult)
|
||||
mockedIap.getAvailablePurchases.mockReset().mockResolvedValue([{
|
||||
id: 'order-1',
|
||||
productId: PRODUCT_ID,
|
||||
purchaseToken: TOKEN,
|
||||
purchaseState: 'purchased',
|
||||
store: 'google',
|
||||
transactionDate: Date.parse('2026-08-21T00:00:00.000Z'),
|
||||
quantity: 1,
|
||||
isAutoRenewing: true,
|
||||
packageNameAndroid: 'com.d3ro.voice',
|
||||
}])
|
||||
mockedSupabase.functions.invoke.mockReset().mockResolvedValue({
|
||||
data: {
|
||||
purchase: { purchase_id: PURCHASE_ID, acknowledged: true },
|
||||
verification: {
|
||||
product_id: PRODUCT_ID,
|
||||
purchase_state: 'purchased',
|
||||
entitled: true,
|
||||
acknowledged: true,
|
||||
},
|
||||
finish_transaction: false,
|
||||
server_acknowledged: true,
|
||||
},
|
||||
error: null,
|
||||
})
|
||||
mockedEntitlement.useEntitlement.mockReturnValue({
|
||||
snapshot: {
|
||||
tier: 'free',
|
||||
provider: 'none',
|
||||
storeProductId: null,
|
||||
},
|
||||
refresh: jest.fn(async () => ({
|
||||
tier: 'pro',
|
||||
status: 'active',
|
||||
provider: 'google_play',
|
||||
paymentProvider: 'google_play',
|
||||
currentPeriodStart: '2026-08-21T00:00:00.000Z',
|
||||
currentPeriodEnd: '2026-09-21T00:00:00.000Z',
|
||||
cancelAt: null,
|
||||
autoRenewing: true,
|
||||
storeProductId: PRODUCT_ID,
|
||||
overageCredits: 0,
|
||||
usageToday: {},
|
||||
purchases: [{
|
||||
id: PURCHASE_ID,
|
||||
platform: 'google_play',
|
||||
productId: PRODUCT_ID,
|
||||
state: 'purchased',
|
||||
purchaseAt: '2026-08-21T00:00:00.000Z',
|
||||
expiresAt: '2026-09-21T00:00:00.000Z',
|
||||
autoRenewing: true,
|
||||
verifiedAt: '2026-08-21T00:00:01.000Z',
|
||||
}],
|
||||
adFree: true,
|
||||
refreshedAt: '2026-08-21T00:00:02.000Z',
|
||||
})),
|
||||
})
|
||||
})
|
||||
|
||||
test('awaits the direct store list, server verification, and refreshed entitlement', async () => {
|
||||
let renderer: ReactTestRenderer.ReactTestRenderer
|
||||
await act(async () => {
|
||||
renderer = ReactTestRenderer.create(
|
||||
<BillingProvider><Probe /></BillingProvider>,
|
||||
)
|
||||
await Promise.resolve()
|
||||
})
|
||||
|
||||
await act(async () => {
|
||||
await latestBilling!.restore()
|
||||
})
|
||||
|
||||
expect(latestBilling).toMatchObject({
|
||||
connected: true,
|
||||
storeAvailable: true,
|
||||
operation: 'succeeded',
|
||||
errorCode: null,
|
||||
})
|
||||
expect(mockedIap.getAvailablePurchases).toHaveBeenCalledWith({
|
||||
includeSuspendedAndroid: true,
|
||||
})
|
||||
expect(mockedIap.__stableUseIapResult.restorePurchases).not.toHaveBeenCalled()
|
||||
expect(mockedSupabase.functions.invoke).toHaveBeenCalledWith('iap-verify', {
|
||||
body: {
|
||||
platform: 'google_play',
|
||||
productId: PRODUCT_ID,
|
||||
purchaseToken: TOKEN,
|
||||
},
|
||||
})
|
||||
expect(mockedEntitlement.useEntitlement.mock.results[0].value.refresh)
|
||||
.toHaveBeenCalledTimes(1)
|
||||
act(() => renderer!.unmount())
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue