Skip to content

Commit fb45852

Browse files
VmMadKeitoTadashi
andauthored
feat(dapp): add coupon tests (#1035)
* feat(dapp): add coupon tests * fix: add missing util * chore: remove old createCoupon and use new methods --------- Co-authored-by: Jose Luis Torres Corbalan <jltorres@boxfish.studio> Co-authored-by: Keito <64607484+KeitoTadashi@users.noreply.github.com>
1 parent ddf2769 commit fb45852

File tree

4 files changed

+125
-51
lines changed

4 files changed

+125
-51
lines changed

dapp/tests/management/management.spec.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,20 @@
44
import { resolve } from 'path';
55
import { normalizeIotaName } from '@iota/iota-names-sdk';
66
import { Ed25519Keypair } from '@iota/iota-sdk/keypairs/ed25519';
7-
import { formatAddress, NANOS_PER_IOTA } from '@iota/iota-sdk/utils';
7+
import { formatAddress } from '@iota/iota-sdk/utils';
88

99
import { formatDate } from '@/lib/utils/format/formatDate';
1010

1111
import { expect, test } from '../helpers/fixtures';
12+
import { addToCouponList } from '../setup/toggleSmartContract';
1213
import { iotaNamesClient } from '../setup/utils';
1314
import {
1415
addSubnameName,
1516
connectName,
1617
connectWallet,
17-
createCoupon,
1818
createWallet,
1919
editSetup,
20+
generateRandomCoupon,
2021
generateRandomName,
2122
generateRandomSubname,
2223
getAddressByIndexPath,
@@ -664,12 +665,8 @@ test.describe.serial('Name Management Tests', () => {
664665
const responsePurchase = await purchaseName(name, keypair);
665666
expect(responsePurchase.effects?.status.status).toBe('success');
666667

667-
const couponCode = 'FIXED_1000';
668-
await createCoupon({
669-
code: couponCode,
670-
type: 'fixed',
671-
value: BigInt(1000) * NANOS_PER_IOTA,
672-
});
668+
const couponCode = generateRandomCoupon('E2E100OFF');
669+
await addToCouponList(couponCode);
673670

674671
await page.goto('/my-names');
675672
await expect(

dapp/tests/purchase/purchase.spec.ts

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,14 @@ import { expect } from '@playwright/test';
88
import { denormalizeName } from '@/lib/utils';
99

1010
import { test } from '../helpers/fixtures';
11-
import { connectWallet, createWallet, generateRandomName, requestFaucetTokens } from '../utils';
11+
import { addToCouponList } from '../setup/toggleSmartContract';
12+
import {
13+
connectWallet,
14+
createWallet,
15+
generateRandomCoupon,
16+
generateRandomName,
17+
requestFaucetTokens,
18+
} from '../utils';
1219

1320
test.describe.serial('Purchase Name Tests', () => {
1421
test.beforeAll(async ({ appPage, context, extensionPage, extensionName, sharedState }) => {
@@ -102,6 +109,61 @@ test.describe.serial('Purchase Name Tests', () => {
102109
await expect(nameMessage).toContainText('Name is already taken.');
103110
});
104111

112+
test('Test with a coupon', async ({ appPage: page, context }) => {
113+
const couponCode = generateRandomCoupon('E2E100OFF');
114+
const name = generateRandomName('coupon');
115+
116+
await addToCouponList(couponCode);
117+
await page.getByPlaceholder('Search for your IOTA name').click();
118+
await page.getByPlaceholder('Check name availability').fill(name);
119+
120+
const purchaseCardLocator = page.getByTestId('purchase-name-card');
121+
122+
await purchaseCardLocator.waitFor({ state: 'visible', timeout: 10_000 });
123+
await purchaseCardLocator.hover();
124+
await purchaseCardLocator.getByRole('button', { name: 'Buy' }).click({ timeout: 10_000 });
125+
await expect(page.getByTestId('name-purchase-title')).toContainText(
126+
'@' + denormalizeName(name),
127+
);
128+
129+
await page.getByPlaceholder('Have a discount code?').fill(couponCode);
130+
await page.getByRole('button', { name: '+ Apply Coupon' }).click();
131+
132+
await expect(page.getByRole('button', { name: couponCode })).toBeVisible({
133+
timeout: 5_000,
134+
});
135+
136+
await page.getByRole('button', { name: 'Buy' }).click({ timeout: 10_000 });
137+
(await context.waitForEvent('page')).getByRole('button', { name: 'Approve' }).click();
138+
139+
await expect(page.getByText('Successfully registered name')).toBeVisible({
140+
timeout: 30_000,
141+
});
142+
});
143+
144+
test('Test with an invalid coupon', async ({ appPage: page, context }) => {
145+
const couponCode = generateRandomCoupon('E2E100OFF');
146+
const name = generateRandomName('invalid');
147+
148+
await page.getByPlaceholder('Search for your IOTA name').click();
149+
await page.getByPlaceholder('Check name availability').fill(name);
150+
151+
const purchaseCardLocator = page.getByTestId('purchase-name-card');
152+
153+
await purchaseCardLocator.waitFor({ state: 'visible', timeout: 10_000 });
154+
await purchaseCardLocator.hover();
155+
await purchaseCardLocator.getByRole('button', { name: 'Buy' }).click({ timeout: 10_000 });
156+
await expect(page.getByTestId('name-purchase-title')).toContainText(
157+
'@' + denormalizeName(name),
158+
);
159+
160+
await page.getByPlaceholder('Have a discount code?').fill(couponCode);
161+
await page.getByRole('button', { name: '+ Apply Coupon' }).click();
162+
163+
await expect(page.getByText('Invalid coupon')).toBeVisible({
164+
timeout: 5_000,
165+
});
166+
});
105167
test('Buy with a different expiration time', async ({ appPage: page, context }) => {
106168
const YEARS = 5;
107169
// Name bought when initializing localnet with scripts/tests/register-name.ts

dapp/tests/setup/toggleSmartContract.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@
22
// SPDX-License-Identifier: Apache-2.0
33

44
import { Transaction } from '@iota/iota-sdk/transactions';
5+
import { blake2b } from '@noble/hashes/blake2';
6+
import { bytesToHex } from '@noble/hashes/utils';
57
import { expect } from '@playwright/test';
68

79
import { adminKeypair, client, getAuthorizedSmartContractTypes, iotaNamesClient } from './utils';
810

911
const envs = {
12+
COUPONS: iotaNamesClient.getPackage('couponsPackageId'),
1013
IOTA_NAMES_PACKAGE_ADDRESS: iotaNamesClient.getPackage('packageId'),
1114
IOTA_NAMES_OBJECT_ID: iotaNamesClient.getPackage('iotaNamesObjectId'),
1215
ADMIN_CAP: iotaNamesClient.getPackage('adminCap'),
@@ -95,3 +98,53 @@ export async function toggleSmartContractMode(mode: 'auctions' | 'purchases'): P
9598
expect(finalState.isAuctionAuthorized).toBe(targetState.isAuctionAuthorized);
9699
expect(finalState.isPaymentAuthorized).toBe(targetState.isPaymentAuthorized);
97100
}
101+
102+
export async function addToCouponList(coupon: string): Promise<void> {
103+
if (!process.env.ADMIN_MNEMONIC) {
104+
throw new Error('env ADMIN_MNEMONIC not set. Cannot toggle smart contract mode.');
105+
}
106+
107+
const target = `${envs.COUPONS}::coupon_house::admin_add_percentage_coupon`;
108+
const rulesTarget = `${envs.COUPONS}::rules::new_empty_rules`;
109+
110+
const tx = new Transaction();
111+
112+
const [rules] = tx.moveCall({
113+
target: rulesTarget,
114+
arguments: [],
115+
});
116+
const couponCodeHash = bytesToHex(blake2b(coupon, { dkLen: 32 }));
117+
118+
tx.moveCall({
119+
target,
120+
arguments: [
121+
tx.object(envs.ADMIN_CAP),
122+
tx.object(envs.IOTA_NAMES_OBJECT_ID),
123+
tx.pure.string(couponCodeHash),
124+
tx.pure.u64(100),
125+
tx.object(rules),
126+
],
127+
});
128+
129+
tx.setSender(adminKeypair.toIotaAddress());
130+
131+
const resp = await client.signAndExecuteTransaction({
132+
transaction: await tx.build({
133+
client,
134+
}),
135+
signer: adminKeypair,
136+
options: {
137+
showEffects: true,
138+
},
139+
});
140+
141+
if (resp.effects?.status.status === 'failure') {
142+
throw new Error(
143+
`Failed to add coupon ${coupon} to coupon list: ${JSON.stringify(resp.effects.status)}`,
144+
);
145+
}
146+
147+
await client.waitForTransaction({
148+
digest: resp.digest,
149+
});
150+
}

dapp/tests/utils.ts

Lines changed: 4 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,11 @@ import type { BrowserContext, Page } from '@playwright/test';
77

88
import 'dotenv/config';
99

10-
import { execFileSync, execSync } from 'child_process';
10+
import { execFileSync } from 'child_process';
1111
import { IotaNamesTransaction, isSubname, NameRecord } from '@iota/iota-names-sdk';
1212
import type { Signer } from '@iota/iota-sdk/cryptography';
1313
import { Transaction } from '@iota/iota-sdk/transactions';
1414
import { NANOS_PER_IOTA } from '@iota/iota-sdk/utils';
15-
import { blake2b } from '@noble/hashes/blake2';
16-
import { bytesToHex } from '@noble/hashes/utils';
1715

1816
import { buildCreateAuctionTransaction, buildPlaceBidTransaction } from '@/auctions';
1917
import { CONFIG } from '@/config';
@@ -445,45 +443,9 @@ export function generateRandomSubname(subname: string, parentName: string) {
445443
return `${subname}${random}.${parentName}`;
446444
}
447445

448-
interface CreateCouponOptions {
449-
code: string;
450-
type: 'fixed' | 'percentage';
451-
value: bigint;
452-
}
453-
454-
export async function createCoupon({ code, type, value }: CreateCouponOptions) {
455-
const couponCodeHash = bytesToHex(blake2b(code, { dkLen: 32 }));
456-
457-
const couponsPackageId = iotaNamesClient.getPackage('couponsPackageId');
458-
const iotaNamesObjectId = iotaNamesClient.getPackage('iotaNamesObjectId');
459-
const adminCapId = iotaNamesClient.getPackage('adminCap');
460-
const adminAddress = iotaNamesClient.getPackage('adminAddress');
461-
462-
const functionName =
463-
type === 'fixed' ? 'admin_add_fixed_coupon' : 'admin_add_percentage_coupon';
464-
465-
const command = `iota client ptb \
466-
--move-call ${couponsPackageId}::rules::new_empty_rules \
467-
--assign empty_rules \
468-
--move-call ${couponsPackageId}::coupon_house::${functionName} \
469-
@${adminCapId} \
470-
@${iotaNamesObjectId} \
471-
'"${couponCodeHash}"' \
472-
${value.toString()} \
473-
empty_rules \
474-
--gas-budget 50000000 \
475-
--sender @${adminAddress}`;
476-
477-
console.log(`Creating ${type} coupon "${code}" with hash ${couponCodeHash}...`);
478-
479-
try {
480-
const output = execSync(command, { encoding: 'utf-8' });
481-
console.log(`Created ${type} coupon "${code}" successfully`);
482-
return output;
483-
} catch (error) {
484-
console.error(`Failed to create coupon "${code}":`, error);
485-
throw error;
486-
}
446+
export function generateRandomCoupon(coupon: string) {
447+
const random = Math.floor(Math.random() * 10_000);
448+
return `${coupon}${random}`.toUpperCase();
487449
}
488450

489451
export async function checkAddressBalanceWithRetries(address: string): Promise<void> {

0 commit comments

Comments
 (0)