d3ro-voice/server/supabase/functions/_shared/google-pubsub.test.ts
2026-08-29 18:33:45 +09:00

88 lines
3.3 KiB
TypeScript

import {
GooglePubSubError,
parseGooglePlayRtdn,
validateGoogleOidcClaims,
} from './google-pubsub.ts'
function assert(condition: boolean, message: string): asserts condition {
if (!condition) throw new Error(message)
}
Deno.test('Google Pub/Sub OIDC claims require exact audience and service identity', () => {
validateGoogleOidcClaims({
aud: 'https://project.functions.supabase.co/google-play-rtdn',
email: 'pubsub-push@example.iam.gserviceaccount.com',
email_verified: 'true',
exp: '2000000000',
iss: 'https://accounts.google.com',
}, 'https://project.functions.supabase.co/google-play-rtdn',
'pubsub-push@example.iam.gserviceaccount.com', 1900000000)
for (const claims of [
{ aud: 'wrong', email: 'pubsub-push@example.iam.gserviceaccount.com', email_verified: true, exp: 2000000000, iss: 'https://accounts.google.com' },
{ aud: 'https://project.functions.supabase.co/google-play-rtdn', email: 'attacker@example.test', email_verified: true, exp: 2000000000, iss: 'https://accounts.google.com' },
{ aud: 'https://project.functions.supabase.co/google-play-rtdn', email: 'pubsub-push@example.iam.gserviceaccount.com', email_verified: true, exp: 1800000000, iss: 'https://accounts.google.com' },
]) {
let error: unknown
try {
validateGoogleOidcClaims(
claims,
'https://project.functions.supabase.co/google-play-rtdn',
'pubsub-push@example.iam.gserviceaccount.com',
1900000000,
)
} catch (candidate) {
error = candidate
}
assert(error instanceof GooglePubSubError, 'invalid OIDC claims must fail closed')
}
})
Deno.test('Google Play RTDN parser accepts only the canonical package and subscription payload', () => {
const payload = {
version: '1.0',
packageName: 'com.d3ro.voice',
eventTimeMillis: '1787241600000',
subscriptionNotification: {
version: '1.0',
notificationType: 3,
purchaseToken: 'registered-google-play-token',
},
}
const notification = parseGooglePlayRtdn({
message: {
messageId: 'pubsub-message-1',
data: btoa(JSON.stringify(payload)),
},
})
assert(notification.kind === 'subscription', 'subscription payload must be discriminated')
assert(notification.packageName === 'com.d3ro.voice', 'package must be canonical')
assert(notification.notificationType === 3, 'notification type must be preserved')
assert(notification.purchaseToken === 'registered-google-play-token', 'token must be preserved internally')
payload.packageName = 'com.attacker.app'
let error: unknown
try {
parseGooglePlayRtdn({
message: { messageId: 'pubsub-message-2', data: btoa(JSON.stringify(payload)) },
})
} catch (candidate) {
error = candidate
}
assert(error instanceof GooglePubSubError, 'wrong package must fail closed')
})
Deno.test('Google Play RTDN test notification is acknowledged without a purchase token', () => {
const notification = parseGooglePlayRtdn({
message: {
messageId: 'pubsub-test-message',
data: btoa(JSON.stringify({
version: '1.0',
packageName: 'com.d3ro.voice',
eventTimeMillis: '1787241600000',
testNotification: { version: '1.0' },
})),
},
})
assert(notification.kind === 'test', 'test notification must be recognized')
})