fix(cloud-store): align gift cards with cloud model

This commit is contained in:
saltbo
2026-05-11 10:10:04 -04:00
parent 736fd00cc4
commit 0c070d8a83
13 changed files with 91 additions and 109 deletions
+6 -11
View File
@@ -24,11 +24,6 @@ type CloudGiftCard = {
code: string
}
type CloudGiftCardPage = {
items: CloudGiftCard[]
total: number
}
type CloudOrder = {
id: string
paymentStatus: string
@@ -226,13 +221,13 @@ async function createOneTimePackage(page: Page, name: string) {
}
async function createGiftCard(page: Page) {
const cards = await postJson<CloudGiftCardPage>(page, '/api/admin/store/gift-cards', {
const cards = await postJson<CloudGiftCard[]>(page, '/api/admin/store/gift-cards', {
amount: 200,
currency: 'usd',
count: 1,
})
expect(cards.items.length).toBe(1)
return cards.items[0]
expect(cards.length).toBe(1)
return cards[0]
}
async function createOneTimePackageThroughUi(page: Page, packageName: string) {
@@ -271,9 +266,9 @@ async function createGiftCardThroughUi(page: Page) {
await dialog.getByRole('button', { name: 'Generate' }).click()
const result = await response
expect(result.status()).toBe(201)
const cards = (await result.json()) as CloudGiftCardPage
expect(cards.items.length).toBe(1)
return cards.items[0].code
const cards = (await result.json()) as CloudGiftCard[]
expect(cards.length).toBe(1)
return cards[0].code
}
async function expectAdminProductVisibleInApi(page: Page, packageName: string) {
+8 -10
View File
@@ -140,15 +140,14 @@ describe('quota store helper paths', () => {
items: [
{
id: 'gift-1',
boundLicenseId: null,
code: 'ZS-PAGED-1',
storeId: 'store-1',
campaignId: null,
code: null,
codeLast4: 'GED1',
amount: 2500,
currency: 'usd',
status: 'active',
expiresAt: null,
firstRedeemedAt: '2026-05-07T00:00:30.000Z',
lastRedeemedAt: '2026-05-07T00:01:00.000Z',
redemptionCount: 1,
createdAt: '2026-05-07T00:00:00.000Z',
updatedAt: '2026-05-07T00:01:00.000Z',
disabledAt: null,
@@ -162,15 +161,14 @@ describe('quota store helper paths', () => {
items: [
{
id: 'gift-1',
boundLicenseId: null,
code: 'ZS-PAGED-1',
storeId: 'store-1',
campaignId: null,
code: null,
codeLast4: 'GED1',
amount: 2500,
currency: 'usd',
status: 'active',
expiresAt: null,
firstRedeemedAt: '2026-05-07T00:00:30.000Z',
lastRedeemedAt: '2026-05-07T00:01:00.000Z',
redemptionCount: 1,
createdAt: '2026-05-07T00:00:00.000Z',
updatedAt: '2026-05-07T00:01:00.000Z',
disabledAt: null,
+6 -7
View File
@@ -169,16 +169,14 @@ export const cloudStoreOrdersQuerySchema = cloudOrdersQuerySchema
export const cloudGiftCardSchema = z.object({
id: z.string().min(1),
storeId: z.string().min(1).nullable().optional(),
boundLicenseId: z.string().min(1).nullable(),
code: z.string().min(1),
storeId: z.string().min(1),
campaignId: z.string().min(1).nullable(),
code: z.string().min(1).nullable(),
codeLast4: z.string().min(1),
amount: z.number().int().min(0),
currency: z.string().min(1),
status: z.enum(['created', 'active', 'disabled', 'exhausted', 'expired', 'revoked']),
status: z.enum(['active', 'redeemed', 'disabled', 'expired', 'revoked']),
expiresAt: z.string().nullable(),
firstRedeemedAt: z.string().nullable(),
lastRedeemedAt: z.string().nullable(),
redemptionCount: z.number().int().min(0),
createdAt: z.string().min(1),
updatedAt: z.string().min(1),
disabledAt: z.string().nullable(),
@@ -190,6 +188,7 @@ export const cloudGiftCardsResponseSchema = z.object({
items: z.array(cloudGiftCardSchema),
total: z.number().int().min(0),
})
export const cloudGiftCardListSchema = z.array(cloudGiftCardSchema)
export const giftCardListQuerySchema = z.object({ status: giftCardStatusSchema.optional() })
export async function getUserStoreSettings(db: Parameters<typeof getRequiredSettings>[0]) {
+26 -33
View File
@@ -18,15 +18,14 @@ const { secretKey: EVENT_SECRET, publicKey: EVENT_PUBLIC } = generateKeys('publi
const zpanCloudGiftCardResponseFixture: CloudGiftCard = {
id: 'gift-card-1',
boundLicenseId: null,
code: 'ZS11-ACTV-0000-0001',
storeId: 'store-test-binding',
campaignId: null,
code: null,
codeLast4: '0001',
amount: 1000,
currency: 'usd',
status: 'active',
expiresAt: null,
firstRedeemedAt: null,
lastRedeemedAt: null,
redemptionCount: 0,
createdAt: '2026-05-06T00:00:00.000Z',
updatedAt: '2026-05-06T00:00:00.000Z',
disabledAt: null,
@@ -205,12 +204,12 @@ beforeEach(() => {
ok: true,
status: 200,
json: async () => ({
items: [cloudGiftCard({ code: 'ZS-LIST-1', amount: 1024, redemptionCount: 1 })],
items: [cloudGiftCard({ code: 'ZS-LIST-1', codeLast4: 'ST-1', amount: 1024 })],
total: 1,
limit: 50,
offset: 0,
data: {
items: [cloudGiftCard({ code: 'ZS-LIST-1', amount: 1024, redemptionCount: 1 })],
items: [cloudGiftCard({ code: 'ZS-LIST-1', codeLast4: 'ST-1', amount: 1024 })],
total: 1,
limit: 50,
offset: 0,
@@ -223,16 +222,14 @@ beforeEach(() => {
ok: true,
status: 201,
json: async () => ({
data: {
items: Array.from({ length: body.count ?? 1 }, (_, index) =>
cloudGiftCard({
code: `ZS-GEN-${index + 1}`,
amount: body.amount ?? 1024,
status: 'created',
}),
),
total: body.count ?? 1,
},
data: Array.from({ length: body.count ?? 1 }, (_, index) =>
cloudGiftCard({
code: `ZS-GEN-${index + 1}`,
codeLast4: `GEN${index + 1}`,
amount: body.amount ?? 1024,
status: 'active',
}),
),
}),
} as Response
}
@@ -418,8 +415,8 @@ describe('Quota Store API', () => {
items: [
cloudGiftCard({
code: 'ZS11-ACTV-0000-0001',
codeLast4: '0001',
amount: 2048,
redemptionCount: 1,
currency: 'usd',
}),
],
@@ -429,15 +426,14 @@ describe('Quota Store API', () => {
items: [
{
id: 'gift-card-1',
boundLicenseId: null,
storeId: 'store-test-binding',
campaignId: null,
code: 'ZS11-ACTV-0000-0001',
codeLast4: '0001',
amount: 2048,
currency: 'usd',
status: 'active',
expiresAt: null,
firstRedeemedAt: null,
lastRedeemedAt: null,
redemptionCount: 1,
createdAt: '2026-05-06T00:00:00.000Z',
updatedAt: '2026-05-06T00:00:00.000Z',
disabledAt: null,
@@ -1304,17 +1300,14 @@ describe('Quota Store API', () => {
const deleted = await app.request('/api/admin/store/gift-cards/ZS-GEN-1', { method: 'DELETE', headers })
expect(generated.status).toBe(201)
await expect(generated.json()).resolves.toMatchObject({
total: 2,
items: [
{ code: 'ZS-GEN-1', amount: 4096, status: 'created' },
{ code: 'ZS-GEN-2', amount: 4096, status: 'created' },
],
})
await expect(generated.json()).resolves.toMatchObject([
{ code: 'ZS-GEN-1', amount: 4096, status: 'active' },
{ code: 'ZS-GEN-2', amount: 4096, status: 'active' },
])
expect(listed.status).toBe(200)
await expect(listed.json()).resolves.toMatchObject({
total: 1,
items: [{ code: 'ZS-LIST-1', amount: 1024, redemptionCount: 1 }],
items: [{ code: 'ZS-LIST-1', amount: 1024 }],
})
expect(deleted.status).toBe(200)
await expect(deleted.json()).resolves.toEqual({ code: 'ZS-GEN-1', deleted: true })
@@ -1354,7 +1347,7 @@ describe('Quota Store API', () => {
await expect(res.json()).resolves.toMatchObject({ total: 9, items: [{ code: 'ZS-PAGED-1' }] })
})
it('returns paged admin gift card create responses from Cloud', async () => {
it('returns admin gift card create responses from Cloud', async () => {
const { app, db } = await createTestApp()
await seedProLicense(db)
const headers = await adminHeaders(app)
@@ -1362,7 +1355,7 @@ describe('Quota Store API', () => {
vi.mocked(fetch).mockResolvedValueOnce({
ok: true,
status: 201,
json: async () => ({ items: [cloudGiftCard({ code: 'ZS-PAGED-1' })], total: 1 }),
json: async () => [cloudGiftCard({ code: 'ZS-CREATED-1', codeLast4: 'TED1' })],
} as Response)
const res = await app.request('/api/admin/store/gift-cards', {
@@ -1376,7 +1369,7 @@ describe('Quota Store API', () => {
})
expect(res.status).toBe(201)
await expect(res.json()).resolves.toMatchObject({ total: 1, items: [{ code: 'ZS-PAGED-1' }] })
await expect(res.json()).resolves.toMatchObject([{ code: 'ZS-CREATED-1' }])
})
it('disables admin gift cards through Cloud', async () => {
+2 -1
View File
@@ -13,6 +13,7 @@ import type { Env } from '../../middleware/platform'
import { requireFeature } from '../../middleware/require-feature'
import { getCloudStoreSettings, upsertCloudStoreSettings } from '../../services/cloud-store'
import {
cloudGiftCardListSchema,
cloudGiftCardsResponseSchema,
cloudOrdersQuerySchema,
cloudPackageListResponseSchema,
@@ -152,7 +153,7 @@ export const adminCloudStore = new Hono<Env>()
return c.json(result)
})
.post('/gift-cards', zValidator('json', createGiftCardInputSchema), async (c) => {
const result = await postCloudWithBinding(c, giftCardsPath(), c.req.valid('json'), cloudGiftCardsResponseSchema)
const result = await postCloudWithBinding(c, giftCardsPath(), c.req.valid('json'), cloudGiftCardListSchema)
if ('error' in result) return c.json(result, 502)
return c.json(result, 201)
})
+1 -1
View File
@@ -160,7 +160,7 @@ export const checkoutInputSchema = z.object({
priceId: z.string().min(1).optional(),
})
export const giftCardStatusSchema = z.enum(['created', 'active', 'disabled', 'exhausted', 'expired', 'revoked'])
export const giftCardStatusSchema = z.enum(['active', 'redeemed', 'disabled', 'expired', 'revoked'])
export const createGiftCardInputSchema = z.object({
amount: z.number().int().positive(),
+5 -7
View File
@@ -164,16 +164,14 @@ export interface CloudOrder {
export interface CloudGiftCard {
id: string
storeId?: string | null
boundLicenseId: string | null
code: string
storeId: string
campaignId: string | null
code: string | null
codeLast4: string
amount: number
currency: string
status: 'created' | 'active' | 'disabled' | 'exhausted' | 'expired' | 'revoked'
status: 'active' | 'redeemed' | 'disabled' | 'expired' | 'revoked'
expiresAt: string | null
firstRedeemedAt: string | null
lastRedeemedAt: string | null
redemptionCount: number
disabledAt: string | null
revokedAt: string | null
createdAt: string
+18 -17
View File
@@ -196,7 +196,7 @@ function CodeStatusSelect({ status, onStatusChange }: Pick<CodeListProps, 'statu
<SelectValue />
</SelectTrigger>
<SelectContent>
{(['all', 'created', 'active', 'disabled', 'exhausted', 'expired', 'revoked'] as const).map((value) => (
{(['all', 'active', 'redeemed', 'disabled', 'expired', 'revoked'] as const).map((value) => (
<SelectItem key={value} value={value}>
{t(`admin.cloudStore.codes.status.${value}`)}
</SelectItem>
@@ -213,7 +213,7 @@ function CodeTable(props: CodeListProps) {
<Table>
<TableHeader>
<TableRow>
{['code', 'storage', 'uses', 'expires', 'statusLabel'].map((key) => (
{['code', 'storage', 'expires', 'statusLabel'].map((key) => (
<TableHead key={key}>{t(`admin.cloudStore.codes.${key}`)}</TableHead>
))}
<TableHead className="text-right">{t('common.actions')}</TableHead>
@@ -221,7 +221,7 @@ function CodeTable(props: CodeListProps) {
</TableHeader>
<TableBody>
{props.codes.map((code) => (
<CodeRow key={code.code} code={code} {...props} />
<CodeRow key={code.id} code={code} {...props} />
))}
</TableBody>
</Table>
@@ -240,13 +240,14 @@ function CodeRow({
const { t } = useTranslation()
const [disableConfirmOpen, setRevokeConfirmOpen] = useState(false)
const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false)
const canDisable = code.status === 'created' || code.status === 'active'
const canDelete = code.status === 'created'
const actionTarget = code.code ?? code.id
const codeLabel = code.code ?? code.codeLast4
const canDisable = code.status === 'active'
const canDelete = code.status === 'active'
return (
<TableRow>
<TableCell className="font-mono text-xs">{code.code}</TableCell>
<TableCell className="font-mono text-xs">{codeLabel}</TableCell>
<TableCell>{formatMoney(code.amount, code.currency)}</TableCell>
<TableCell>{code.redemptionCount}</TableCell>
<TableCell>{code.expiresAt ? new Date(code.expiresAt).toLocaleString() : '-'}</TableCell>
<TableCell>
<CodeStatusBadge code={code} />
@@ -256,7 +257,7 @@ function CodeRow({
<Button
variant="outline"
size="sm"
disabled={!available || !canDisable || disablingGiftCard === code.code}
disabled={!available || !canDisable || disablingGiftCard === actionTarget}
onClick={() => setRevokeConfirmOpen(true)}
>
<Ban className="mr-2 h-4 w-4" />
@@ -265,7 +266,7 @@ function CodeRow({
<Button
variant="outline"
size="sm"
disabled={!available || !canDelete || deletingGiftCard === code.code}
disabled={!available || !canDelete || deletingGiftCard === actionTarget}
onClick={() => setDeleteConfirmOpen(true)}
>
<Trash2 className="mr-2 h-4 w-4" />
@@ -276,21 +277,21 @@ function CodeRow({
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>{t('admin.cloudStore.codes.disableTitle')}</DialogTitle>
<DialogDescription>{t('admin.cloudStore.codes.disableConfirm', { code: code.code })}</DialogDescription>
<DialogDescription>{t('admin.cloudStore.codes.disableConfirm', { code: codeLabel })}</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button
variant="outline"
disabled={disablingGiftCard === code.code}
disabled={disablingGiftCard === actionTarget}
onClick={() => setRevokeConfirmOpen(false)}
>
{t('common.cancel')}
</Button>
<Button
variant="destructive"
disabled={disablingGiftCard === code.code}
disabled={disablingGiftCard === actionTarget}
onClick={() => {
onRevoke(code.code)
onRevoke(actionTarget)
setRevokeConfirmOpen(false)
}}
>
@@ -303,21 +304,21 @@ function CodeRow({
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>{t('admin.cloudStore.codes.deleteTitle')}</DialogTitle>
<DialogDescription>{t('admin.cloudStore.codes.deleteConfirm', { code: code.code })}</DialogDescription>
<DialogDescription>{t('admin.cloudStore.codes.deleteConfirm', { code: codeLabel })}</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button
variant="outline"
disabled={deletingGiftCard === code.code}
disabled={deletingGiftCard === actionTarget}
onClick={() => setDeleteConfirmOpen(false)}
>
{t('common.cancel')}
</Button>
<Button
variant="destructive"
disabled={deletingGiftCard === code.code}
disabled={deletingGiftCard === actionTarget}
onClick={() => {
onDelete(code.code)
onDelete(actionTarget)
setDeleteConfirmOpen(false)
}}
>
+1 -2
View File
@@ -1116,10 +1116,9 @@
"admin.cloudStore.codes.expires": "Expires",
"admin.cloudStore.codes.statusLabel": "Status",
"admin.cloudStore.codes.status.all": "All",
"admin.cloudStore.codes.status.created": "Created",
"admin.cloudStore.codes.status.active": "Active",
"admin.cloudStore.codes.status.redeemed": "Redeemed",
"admin.cloudStore.codes.status.disabled": "Disabled",
"admin.cloudStore.codes.status.exhausted": "Exhausted",
"admin.cloudStore.codes.status.expired": "Expired",
"admin.cloudStore.codes.status.revoked": "Revoked",
"admin.cloudStore.codes.empty": "No gift cards match this filter.",
+1 -2
View File
@@ -1116,10 +1116,9 @@
"admin.cloudStore.codes.expires": "过期时间",
"admin.cloudStore.codes.statusLabel": "状态",
"admin.cloudStore.codes.status.all": "全部",
"admin.cloudStore.codes.status.created": "未使用",
"admin.cloudStore.codes.status.active": "可用",
"admin.cloudStore.codes.status.redeemed": "已兑换",
"admin.cloudStore.codes.status.disabled": "已停用",
"admin.cloudStore.codes.status.exhausted": "已用完",
"admin.cloudStore.codes.status.expired": "已失效",
"admin.cloudStore.codes.status.revoked": "已撤销",
"admin.cloudStore.codes.empty": "没有匹配此筛选条件的礼品卡。",
+2 -2
View File
@@ -408,7 +408,7 @@ describe('api', () => {
it('calls admin gift card and order endpoints', async () => {
vi.mocked(fetch)
.mockResolvedValueOnce(makeResponse({ items: [], total: 0 }))
.mockResolvedValueOnce(makeResponse({ items: [{ code: 'ZS123' }], total: 1 }))
.mockResolvedValueOnce(makeResponse([{ code: 'ZS123' }]))
.mockResolvedValueOnce(makeResponse({ code: 'ZS123', disabled: true }))
.mockResolvedValueOnce(makeResponse({ code: 'ZS123', deleted: true }))
.mockResolvedValueOnce(makeResponse({ items: [], total: 0 }))
@@ -429,7 +429,7 @@ describe('api', () => {
currency: 'usd',
count: 3,
})
expect(createdGiftCards).toEqual({ items: [{ code: 'ZS123' }], total: 1 })
expect(createdGiftCards).toEqual([{ code: 'ZS123' }])
expect(calls[2][0]).toBe('/api/admin/store/gift-cards/ZS123')
expect(calls[2][1].method).toBe('PATCH')
expect(JSON.parse(calls[2][1].body as string)).toEqual({ disabled: true })
+1 -1
View File
@@ -361,7 +361,7 @@ export function listCloudGiftCards(status?: GiftCardStatus) {
}
export function createCloudGiftCards(data: CreateGiftCardInput) {
return unwrap<{ items: CloudGiftCard[]; total: number }>(adminCloudStoreApi['gift-cards'].$post({ json: data }))
return unwrap<CloudGiftCard[]>(adminCloudStoreApi['gift-cards'].$post({ json: data }))
}
export function disableCloudGiftCard(code: string) {
@@ -108,15 +108,14 @@ function quotaPackage(overrides: Partial<CloudProduct> = {}): CloudProduct {
function giftCard(overrides: Partial<CloudGiftCard> = {}): CloudGiftCard {
return {
id: 'gift-card-1',
boundLicenseId: null,
code: 'ZS-CODE-1',
storeId: 'store-1',
campaignId: null,
code: null,
codeLast4: 'ODE1',
amount: 10_000,
currency: 'usd',
status: 'created',
status: 'active',
expiresAt: null,
firstRedeemedAt: null,
lastRedeemedAt: null,
redemptionCount: 0,
disabledAt: null,
revokedAt: null,
createdAt: '2026-05-05T00:00:00.000Z',
@@ -496,14 +495,14 @@ describe('AdminCloudStorePage', () => {
items: [giftCard()],
total: 1,
})
vi.mocked(createCloudGiftCards).mockResolvedValue({ items: [], total: 0 })
vi.mocked(disableCloudGiftCard).mockResolvedValue({ code: 'ZS-CODE-1', disabled: true })
vi.mocked(createCloudGiftCards).mockResolvedValue([])
vi.mocked(disableCloudGiftCard).mockResolvedValue({ code: 'gift-card-1', disabled: true })
const view = renderAdminPage()
await waitFor(() => expect(view.getByRole('tab', { name: 'admin.cloudStore.tabs.codes' })).toBeTruthy())
fireEvent.click(view.getByRole('tab', { name: 'admin.cloudStore.tabs.codes' }))
await waitFor(() => expect(view.getByText('ZS-CODE-1')).toBeTruthy())
await waitFor(() => expect(view.getByText('ODE1')).toBeTruthy())
fireEvent.click(view.getByRole('button', { name: 'admin.cloudStore.codes.generateTitle' }))
const dialog = await view.findByRole('dialog')
fireEvent.change(view.getByLabelText('admin.cloudStore.codes.amount'), { target: { value: '50' } })
@@ -526,7 +525,7 @@ describe('AdminCloudStorePage', () => {
expect(within(disableDialog).getByText('admin.cloudStore.codes.disableTitle')).toBeTruthy()
fireEvent.click(within(disableDialog).getByRole('button', { name: 'admin.cloudStore.codes.disable' }))
await waitFor(() => expect(vi.mocked(disableCloudGiftCard).mock.calls[0][0]).toBe('ZS-CODE-1'))
await waitFor(() => expect(vi.mocked(disableCloudGiftCard).mock.calls[0][0]).toBe('gift-card-1'))
})
it('deletes an eligible gift card from the codes tab', async () => {
@@ -536,13 +535,13 @@ describe('AdminCloudStorePage', () => {
items: [giftCard()],
total: 1,
})
vi.mocked(deleteCloudGiftCard).mockResolvedValue({ code: 'ZS-CODE-1', deleted: true })
vi.mocked(deleteCloudGiftCard).mockResolvedValue({ code: 'gift-card-1', deleted: true })
const view = renderAdminPage()
await waitFor(() => expect(view.getByRole('tab', { name: 'admin.cloudStore.tabs.codes' })).toBeTruthy())
fireEvent.click(view.getByRole('tab', { name: 'admin.cloudStore.tabs.codes' }))
await waitFor(() => expect(view.getByText('ZS-CODE-1')).toBeTruthy())
await waitFor(() => expect(view.getByText('ODE1')).toBeTruthy())
fireEvent.click(view.getByRole('button', { name: 'admin.cloudStore.codes.delete' }))
expect(deleteCloudGiftCard).not.toHaveBeenCalled()
@@ -551,14 +550,14 @@ describe('AdminCloudStorePage', () => {
expect(within(deleteDialog).getByText('admin.cloudStore.codes.deleteTitle')).toBeTruthy()
fireEvent.click(within(deleteDialog).getByRole('button', { name: 'admin.cloudStore.codes.delete' }))
await waitFor(() => expect(vi.mocked(deleteCloudGiftCard).mock.calls[0][0]).toBe('ZS-CODE-1'))
await waitFor(() => expect(vi.mocked(deleteCloudGiftCard).mock.calls[0][0]).toBe('gift-card-1'))
})
it('shows gift cards in a table and opens generation fields in a dialog', async () => {
vi.mocked(getCloudStoreSettings).mockResolvedValue(settings())
vi.mocked(listAdminCloudProducts).mockResolvedValue({ items: [], total: 0 })
vi.mocked(listCloudGiftCards).mockResolvedValue({
items: [giftCard({ id: 'gift-card-2', code: 'ZS-CODE-2', amount: 5000, redemptionCount: 2, status: 'active' })],
items: [giftCard({ id: 'gift-card-2', code: null, codeLast4: 'ODE2', amount: 5000, status: 'active' })],
total: 1,
})
@@ -567,7 +566,7 @@ describe('AdminCloudStorePage', () => {
await waitFor(() => expect(view.getByRole('tab', { name: 'admin.cloudStore.tabs.codes' })).toBeTruthy())
fireEvent.click(view.getByRole('tab', { name: 'admin.cloudStore.tabs.codes' }))
await waitFor(() => expect(view.getByText('ZS-CODE-2')).toBeTruthy())
await waitFor(() => expect(view.getByText('ODE2')).toBeTruthy())
expect(view.getByRole('table')).toBeTruthy()
expect(view.getByRole('columnheader', { name: 'admin.cloudStore.codes.code' })).toBeTruthy()
expect(view.queryByLabelText('admin.cloudStore.codes.count')).toBeNull()