forked from FlowwStar/FlowStar
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbatch-create.spec.ts
More file actions
166 lines (133 loc) · 6.71 KB
/
Copy pathbatch-create.spec.ts
File metadata and controls
166 lines (133 loc) · 6.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
import { test, expect, type Page } from '@playwright/test'
// ─── Shared helper: inject xBull mock so the wallet-gated batch page renders ─
async function withWallet(page: Page) {
await page.addInitScript(() => {
localStorage.setItem('walletId', 'xbull')
;(window as any).xBullSDK = {
connect: async () => ({
publicKey: 'GBQTESTWALLETADDRESS000000000000000000000000000000000000',
}),
signXDR: async () => 'AAAAAgAAAAA...dummy-signature...',
}
})
}
function csvFile(name: string, content: string) {
return {
name,
mimeType: 'text/csv',
buffer: Buffer.from(content),
}
}
const RECIPIENT_A = 'GBQTESTWALLETADDRESS000000000000000000000000000000000000'
const RECIPIENT_B = 'GBQTESTWALLETADDRESS000000000000000000000000000000000001'
test.describe('Batch create — CSV upload flow', () => {
test.beforeEach(async ({ page }) => {
await withWallet(page)
await page.goto('/app/create/batch')
await page.waitForLoadState('networkidle')
})
test('valid CSV uploads and previews all rows as valid', async ({ page }) => {
const csv = [
'recipient,amount,start_time,end_time',
`${RECIPIENT_A},100,2025-01-01T00:00:00Z,2025-12-31T00:00:00Z`,
`${RECIPIENT_B},250,2025-02-01T00:00:00Z,2025-11-30T00:00:00Z`,
].join('\n')
await page.locator('#csvFile').setInputFiles(csvFile('batch.csv', csv))
await expect(page.locator('text=Preview rows')).toBeVisible()
await expect(page.locator('text=2 row(s) loaded, 2 valid.')).toBeVisible()
await expect(page.locator('td span:has-text("Valid")')).toHaveCount(2)
await expect(page.locator('button:has-text("Execute batch")')).toBeEnabled()
})
test('CSV with invalid rows surfaces per-row errors and disables execution', async ({ page }) => {
const csv = [
'recipient,amount,start_time,end_time',
`not-a-valid-address,100,2025-01-01T00:00:00Z,2025-12-31T00:00:00Z`,
`${RECIPIENT_A},abc,2025-01-01T00:00:00Z,2025-12-31T00:00:00Z`,
`${RECIPIENT_A},100,2025-12-31T00:00:00Z,2025-01-01T00:00:00Z`,
].join('\n')
await page.locator('#csvFile').setInputFiles(csvFile('invalid-batch.csv', csv))
await expect(page.locator('text=3 row(s) loaded, 0 valid.')).toBeVisible()
await expect(page.locator('text=Invalid recipient address')).toBeVisible()
await expect(page.locator('text=Invalid amount')).toBeVisible()
await expect(page.locator('text=end_time must be after start_time')).toBeVisible()
await expect(page.locator('button:has-text("Execute batch")')).toBeDisabled()
})
test('cliff_time column variant is parsed into the preview', async ({ page }) => {
const csv = [
'recipient,amount,start_time,end_time,cliff_time',
`${RECIPIENT_A},100,2025-01-01T00:00:00Z,2025-12-31T00:00:00Z,2025-03-01T00:00:00Z`,
].join('\n')
await page.locator('#csvFile').setInputFiles(csvFile('cliff-time.csv', csv))
await expect(page.locator('text=1 row(s) loaded, 1 valid.')).toBeVisible()
const cliffCell = page.locator('table tbody tr').first().locator('td').nth(5)
await expect(cliffCell).not.toContainText('none')
})
test('cliff_duration column variant resolves relative to start_time', async ({ page }) => {
const csv = [
'recipient,amount,start_time,end_time,cliff_duration',
`${RECIPIENT_A},100,2025-01-01T00:00:00Z,2025-12-31T00:00:00Z,30d`,
].join('\n')
await page.locator('#csvFile').setInputFiles(csvFile('cliff-duration.csv', csv))
await expect(page.locator('text=1 row(s) loaded, 1 valid.')).toBeVisible()
const cliffCell = page.locator('table tbody tr').first().locator('td').nth(5)
await expect(cliffCell).not.toContainText('none')
})
test('an out-of-range cliff_duration is flagged as a row error', async ({ page }) => {
const csv = [
'recipient,amount,start_time,end_time,cliff_duration',
`${RECIPIENT_A},100,2025-01-01T00:00:00Z,2025-02-01T00:00:00Z,400d`,
].join('\n')
await page.locator('#csvFile').setInputFiles(csvFile('cliff-duration-invalid.csv', csv))
await expect(page.locator('text=1 row(s) loaded, 0 valid.')).toBeVisible()
await expect(
page.locator('text=cliff_duration must land between start_time and end_time'),
).toBeVisible()
})
test('rejects non-csv file extensions before parsing', async ({ page }) => {
await page.locator('#csvFile').setInputFiles({
name: 'batch.txt',
mimeType: 'text/plain',
buffer: Buffer.from('recipient,amount,start_time,end_time'),
})
await expect(page.locator('text=Please upload a .csv file.')).toBeVisible()
})
test('executing a valid batch runs sequentially and reports completion', async ({ page }) => {
const csv = [
'recipient,amount,start_time,end_time',
`${RECIPIENT_A},100,2025-01-01T00:00:00Z,2025-12-31T00:00:00Z`,
`${RECIPIENT_B},250,2025-02-01T00:00:00Z,2025-11-30T00:00:00Z`,
].join('\n')
await page.locator('#csvFile').setInputFiles(csvFile('execute-batch.csv', csv))
await expect(page.locator('text=2 row(s) loaded, 2 valid.')).toBeVisible()
const executeButton = page.locator('button:has-text("Execute batch")')
await expect(executeButton).toBeEnabled()
await executeButton.click()
await expect(page.locator('text=2 / 2 completed')).toBeVisible()
await expect(page.locator('text=Batch create completed')).toBeVisible()
await expect(page.locator('text=2 streams created successfully.')).toBeVisible()
await expect(executeButton).toBeEnabled()
})
test('execute batch is disabled while there are no valid rows', async ({ page }) => {
const csv = [
'recipient,amount,start_time,end_time',
`not-a-valid-address,100,2025-01-01T00:00:00Z,2025-12-31T00:00:00Z`,
].join('\n')
await page.locator('#csvFile').setInputFiles(csvFile('all-invalid.csv', csv))
await expect(page.locator('text=1 row(s) loaded, 0 valid.')).toBeVisible()
await expect(page.locator('button:has-text("Execute batch")')).toBeDisabled()
})
test('streams created by a batch execution appear in the streams list', async ({ page }) => {
const csv = [
'recipient,amount,start_time,end_time',
`${RECIPIENT_A},100,2025-01-01T00:00:00Z,2025-12-31T00:00:00Z`,
].join('\n')
await page.locator('#csvFile').setInputFiles(csvFile('single-row.csv', csv))
await expect(page.locator('text=1 row(s) loaded, 1 valid.')).toBeVisible()
await page.locator('button:has-text("Execute batch")').click()
await expect(page.locator('text=1 / 1 completed')).toBeVisible()
await page.getByRole('link', { name: 'Streams' }).click()
await page.waitForURL('**/app/streams')
await page.locator('[data-testid="streams-search-input"]').fill(RECIPIENT_A)
await expect(page.locator('[data-testid^="stream-card-"]')).toHaveCount(1)
})
})