Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add mocking #167

Merged
merged 16 commits into from
Oct 5, 2023
26 changes: 5 additions & 21 deletions frontend/claim_sdk/solana.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { ClaimInfo, Ecosystem } from './claim'
import { TOKEN_PROGRAM_ID, Token } from '@solana/spl-token'
import { SignedMessage } from './ecosystems/signatures'
import { extractChainId } from './ecosystems/cosmos'
import { fetchFundTransaction } from '../utils/api'

type bump = number
// NOTE: This must be kept in sync with the on-chain program
Expand Down Expand Up @@ -221,7 +222,9 @@ export class TokenDispenserProvider {
proofOfInclusion: Uint8Array[]
signedMessage: SignedMessage | undefined
}[],
funderWallet: Wallet | undefined = undefined
fetchFundTransactionFunction: (
transactions: VersionedTransaction[]
) => Promise<VersionedTransaction[]> = fetchFundTransaction // This argument is only used for testing where we can't call the API
): Promise<Promise<TransactionError | null>[]> {
const txs: VersionedTransaction[] = []

Expand All @@ -238,26 +241,7 @@ export class TokenDispenserProvider {
this.tokenDispenserProgram.provider as anchor.AnchorProvider
).wallet.signAllTransactions(txs)

let txsSignedTwice: VersionedTransaction[] = []
if (funderWallet) {
// This is defined only in testing, where we can't use the API
txsSignedTwice = await funderWallet.signAllTransactions(txsSignedOnce)
} else {
const response = await fetch('/api/grant/v1/fund_transaction', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(
txsSignedOnce.map((tx) => Buffer.from(tx.serialize()))
),
})

txsSignedTwice = (await response.json()).map((serializedTx: any) => {
return VersionedTransaction.deserialize(Buffer.from(serializedTx))
})
}

const txsSignedTwice = await fetchFundTransactionFunction(txsSignedOnce)
// send the txns. Associated token account will be created if needed.
const sendTxs = txsSignedTwice.map(async (signedTx) => {
const signature = await this.connection.sendTransaction(signedTx, {
Expand Down
63 changes: 63 additions & 0 deletions frontend/integration/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { VersionedTransaction } from '@solana/web3.js'
import { NextApiRequest, NextApiResponse } from 'next'
import handlerFundTransaction from '../pages/api/grant/v1/fund_transaction'
import {
getAmountAndProofRoute,
getFundTransactionRoute,
handleAmountAndProofResponse,
handleFundTransaction,
} from '../utils/api'
import { ClaimInfo, Ecosystem } from '../claim_sdk/claim'
import handlerAmountAndProof from '../pages/api/grant/v1/amount_and_proof'

export class NextApiResponseMock {
public jsonBody: any
public statusCode: number = 0

json(jsonBody: any) {
this.jsonBody = jsonBody
}

status(statusCode: number): NextApiResponseMock {
this.statusCode = statusCode
return this
}
}
export async function mockfetchFundTransaction(
transactions: VersionedTransaction[]
): Promise<VersionedTransaction[]> {
const req: NextApiRequest = {
url: getFundTransactionRoute(),
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: transactions.map((tx) => Buffer.from(tx.serialize())),
} as unknown as NextApiRequest
const res = new NextApiResponseMock()

await handlerFundTransaction(req, res as unknown as NextApiResponse)
return handleFundTransaction(res.statusCode, res.jsonBody)
}

/** fetchAmountAndProof but for tests */
export async function mockFetchAmountAndProof(
ecosystem: Ecosystem,
identity: string
): Promise<
{ claimInfo: ClaimInfo; proofOfInclusion: Uint8Array[] } | undefined
> {
const req: NextApiRequest = {
url: getAmountAndProofRoute(ecosystem, identity),
query: { ecosystem, identity },
} as unknown as NextApiRequest
const res = new NextApiResponseMock()

await handlerAmountAndProof(req, res as unknown as NextApiResponse)
return handleAmountAndProofResponse(
ecosystem,
identity,
res.statusCode,
res.jsonBody
)
}
66 changes: 14 additions & 52 deletions frontend/integration/integrationTest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
clearDatabase,
getDatabasePool,
} from '../utils/db'
import { ClaimInfo, Ecosystem } from '../claim_sdk/claim'
import { Ecosystem } from '../claim_sdk/claim'
import { Token, TOKEN_PROGRAM_ID } from '@solana/spl-token'
import {
LAMPORTS_PER_SOL,
Expand All @@ -14,59 +14,18 @@ import {
SYSVAR_INSTRUCTIONS_PUBKEY,
} from '@solana/web3.js'
import { Buffer } from 'buffer'
import { TokenDispenserProvider, airdrop } from '../claim_sdk/solana'
import { TokenDispenserProvider } from '../claim_sdk/solana'
import {
DiscordTestWallet,
TestWallet,
loadAnchorWallet,
loadFunderWallet,
} from '../claim_sdk/testWallets'
import { loadTestWallets } from '../claim_sdk/testWallets'
import { NextApiRequest, NextApiResponse } from 'next'
import {
getAmountAndProofRoute,
handleAmountAndProofResponse,
} from '../utils/api'
import handlerAmountAndProof from '../pages/api/grant/v1/amount_and_proof'
import { mockFetchAmountAndProof, mockfetchFundTransaction } from './api'

const pool = getDatabasePool()

export class NextApiResponseMock {
public jsonBody: any
public statusCode: number = 0

json(jsonBody: any) {
this.jsonBody = jsonBody
}

status(statusCode: number): NextApiResponseMock {
this.statusCode = statusCode
return this
}
}

/** fetchAmountAndProof but for tests */
export async function mockFetchAmountAndProof(
ecosystem: Ecosystem,
identity: string
): Promise<
{ claimInfo: ClaimInfo; proofOfInclusion: Uint8Array[] } | undefined
> {
const req: NextApiRequest = {
url: getAmountAndProofRoute(ecosystem, identity),
query: { ecosystem, identity },
} as unknown as NextApiRequest
const res = new NextApiResponseMock()

await handlerAmountAndProof(req, res as unknown as NextApiResponse)
return handleAmountAndProofResponse(
ecosystem,
identity,
res.statusCode,
res.jsonBody
)
}

describe('integration test', () => {
let root: Buffer
let testWallets: Record<Ecosystem, TestWallet[]>
Expand Down Expand Up @@ -202,7 +161,7 @@ describe('integration test', () => {
signedMessage,
},
],
funderWallet
mockfetchFundTransaction
)
)

Expand Down Expand Up @@ -237,7 +196,7 @@ describe('integration test', () => {
signedMessage,
},
],
funderWallet
mockfetchFundTransaction
)
)

Expand Down Expand Up @@ -288,7 +247,10 @@ describe('integration test', () => {
).toBeFalsy()

await Promise.all(
await tokenDispenserProvider.submitClaims(claims, funderWallet)
await tokenDispenserProvider.submitClaims(
claims,
mockfetchFundTransaction
)
)

expect(
Expand Down Expand Up @@ -334,7 +296,7 @@ describe('integration test', () => {
signedMessage,
},
],
funderWallet
mockfetchFundTransaction
)
)

Expand Down Expand Up @@ -373,7 +335,7 @@ describe('integration test', () => {
signedMessage,
},
],
funderWallet
mockfetchFundTransaction
)
)

Expand Down Expand Up @@ -418,7 +380,7 @@ describe('integration test', () => {
signedMessage,
},
],
funderWallet
mockfetchFundTransaction
)
)

Expand Down Expand Up @@ -464,7 +426,7 @@ describe('integration test', () => {
signedMessage: undefined,
},
],
funderWallet
mockfetchFundTransaction
)
)

Expand Down Expand Up @@ -512,7 +474,7 @@ describe('integration test', () => {
signedMessage,
},
],
funderWallet
mockfetchFundTransaction
)
)

Expand Down
15 changes: 9 additions & 6 deletions frontend/pages/api/grant/v1/fund_transaction.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import NodeWallet from '@coral-xyz/anchor/dist/cjs/nodewallet'
import { Keypair, Version, VersionedTransaction } from '@solana/web3.js'
import { Keypair, VersionedTransaction } from '@solana/web3.js'
import { loadFunderWallet } from '../../../../claim_sdk/testWallets'
import type { NextApiRequest, NextApiResponse } from 'next'

const wallet = new NodeWallet(
Keypair.fromSecretKey(
Uint8Array.from(JSON.parse(process.env.FUNDER_KEYPAIR!))
)
)
const wallet = process.env.FUNDER_KEYPAIR
? new NodeWallet(
Keypair.fromSecretKey(
Uint8Array.from(JSON.parse(process.env.FUNDER_KEYPAIR))
)
)
: loadFunderWallet()

export default async function handlerFundTransaction(
req: NextApiRequest,
Expand Down
33 changes: 32 additions & 1 deletion frontend/utils/api.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import BN from 'bn.js'
import { ClaimInfo, Ecosystem } from '../claim_sdk/claim'
import { HASH_SIZE } from '../claim_sdk/merkleTree'
import { PublicKey } from '@solana/web3.js'
import { PublicKey, VersionedTransaction } from '@solana/web3.js'
import { SignedMessage } from '../claim_sdk/ecosystems/signatures'

function parseProof(proof: string) {
Expand Down Expand Up @@ -114,3 +114,34 @@ export async function fetchEvmBreakdown(
const response = await fetch(getEvmBreakdownRoute(identity))
return handleEvmBreakdown(response.status, await response.json())
}

export function getFundTransactionRoute(): string {
return `/api/grant/v1/fund_transaction`
}

export function handleFundTransaction(
status: number,
data: any
): VersionedTransaction[] {
if (status == 200) {
return data.map((serializedTx: any) => {
return VersionedTransaction.deserialize(Buffer.from(serializedTx))
})
} else {
throw new Error('Failed to fund transaction')
}
}

export async function fetchFundTransaction(
transactions: VersionedTransaction[]
): Promise<VersionedTransaction[]> {
const response = await fetch(getFundTransactionRoute(), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(transactions.map((tx) => Buffer.from(tx.serialize()))),
})

return handleFundTransaction(response.status, await response.json())
}
Loading