mirror of
https://github.com/supabase/supabase.git
synced 2026-10-10 20:05:06 +03:00
| # | Branch | Base | | - | ------ | ---- | | 1 | `feat/storage-versioning-private-alpha` — merged | `master` | | 2 | `feat/storage-versioning/002-bucket-form-fields` | `master` | | 3 | `feat/storage-versioning/003-bucket-modals` ◀ | 2 | | 4 | `feat/storage-versioning/004-object-versions-data` | 3 | | 5 | `feat/storage-versioning/005-file-preview-versions` | 4 | | 6 | `feat/storage-versioning/006-billing-storage-retention` | 5 | | 7 | `feat/storage-versioning/007-archived-objects-data` | 6 | | 8 | `feat/storage-versioning/008-archived-rows` | 7 | | 9 | `feat/storage-versioning/009-archived-preview-pane` | 8 | | 10 | `feat/storage-versioning/010-replace-file` | 9 | ## [3/10] Storage object versioning: wire into the bucket modals **Base:** `feat/storage-versioning/002-bucket-form-fields` (PR 2) ### This PR Mounts the object-versioning form section in the create and edit bucket modals behind the feature preview, and saves it. - create and edit bucket modals spread `bucketVersioningFormFields` into their existing form schema - lifecycle defaults to 30 days / 10 versions - edit adds a confirmation before suspending an actively versioned bucket ## Enabling object versioning on a new bucket and setting lifecycle policies https://github.com/user-attachments/assets/194f8319-4929-432e-8a50-206f180a77a8 ## Edit and suspend object-versioning https://github.com/user-attachments/assets/f31e1d34-9840-4f5a-a269-6a911214742d ## To reproduce 1. Make sure storage versioning is enabled under feature previews > Storage Versioning 2. Open Storage Bucket File explorer 3. create new bucket and enable Object Versioning 4. set lifecycle policy - Noncurrent version expiration: can be either empty or >1 - Retained noncurrent versions: can be either empty or between 1 and 100 and can't exist without "Noncurrent version expiration" 5. Open new bucket with object versioning and test changing lifecycle policies 6. Disabling object-versioning shows proper warning and updates `versioning_status` to SUSPENDED (it can never go back to DISABLED once it has been enabled on a bucket) --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> Co-authored-by: Ivan Vasilov <vasilov.ivan@gmail.com>
140 lines
4.9 KiB
TypeScript
140 lines
4.9 KiB
TypeScript
import { fireEvent, screen, waitFor } from '@testing-library/dom'
|
|
import userEvent from '@testing-library/user-event'
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
import { CreateBucketModal } from './CreateBucketModal'
|
|
import { ProjectContextProvider } from '@/components/layouts/ProjectLayout/ProjectContext'
|
|
import { customRender } from '@/tests/lib/custom-render'
|
|
import { addAPIMock } from '@/tests/lib/msw'
|
|
import { routerMock } from '@/tests/lib/route-mock'
|
|
|
|
vi.mock(`hooks/misc/useCheckPermissions`, () => ({
|
|
useCheckPermissions: vi.fn(),
|
|
useAsyncCheckPermissions: vi.fn().mockImplementation(() => ({ can: true })),
|
|
}))
|
|
|
|
const { versioningEnabled } = vi.hoisted(() => ({ versioningEnabled: { current: false } }))
|
|
|
|
vi.mock(
|
|
'@/components/interfaces/App/FeaturePreview/FeaturePreviewContext',
|
|
async (importOriginal) => {
|
|
const actual = await importOriginal<Record<string, unknown>>()
|
|
return { ...actual, useIsStorageVersioningEnabled: () => versioningEnabled.current }
|
|
}
|
|
)
|
|
|
|
describe(`CreateBucketModal`, () => {
|
|
beforeEach(() => {
|
|
versioningEnabled.current = false
|
|
// useParams
|
|
routerMock.setCurrentUrl(`/project/default/storage/buckets`)
|
|
// useSelectedProject -> Project
|
|
addAPIMock({
|
|
method: `get`,
|
|
path: `/platform/projects/:ref`,
|
|
// @ts-expect-error
|
|
response: {
|
|
cloud_provider: 'localhost',
|
|
id: 1,
|
|
inserted_at: '2021-08-02T06:40:40.646Z',
|
|
name: 'Default Project',
|
|
organization_id: 1,
|
|
ref: 'default',
|
|
region: 'local',
|
|
status: 'ACTIVE_HEALTHY',
|
|
},
|
|
})
|
|
// useBucketCreateMutation
|
|
addAPIMock({
|
|
method: `post`,
|
|
path: `/platform/storage/:ref/buckets`,
|
|
})
|
|
})
|
|
|
|
it(`renders a dialog with a form`, async () => {
|
|
customRender(
|
|
<ProjectContextProvider projectRef="default">
|
|
<CreateBucketModal open={true} onOpenChange={() => {}} />
|
|
</ProjectContextProvider>,
|
|
{
|
|
nuqs: {
|
|
searchParams: {
|
|
new: 'true',
|
|
},
|
|
},
|
|
}
|
|
)
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByRole(`dialog`)).toBeInTheDocument()
|
|
})
|
|
|
|
const nameInput = screen.getByLabelText(`Bucket name`)
|
|
await userEvent.type(nameInput, `test`)
|
|
|
|
const publicToggle = screen.getByLabelText(`Public bucket`)
|
|
expect(publicToggle).not.toBeChecked()
|
|
await userEvent.click(publicToggle)
|
|
expect(publicToggle).toBeChecked()
|
|
|
|
const sizeLimitToggle = screen.getByLabelText(`Restrict file size`)
|
|
expect(sizeLimitToggle).not.toBeChecked()
|
|
await userEvent.click(sizeLimitToggle)
|
|
expect(sizeLimitToggle).toBeChecked()
|
|
|
|
const sizeLimitInput = screen.getByLabelText(`File size limit`)
|
|
expect(sizeLimitInput).toHaveValue(null)
|
|
await userEvent.type(sizeLimitInput, `25`)
|
|
|
|
const sizeLimitUnitSelect = screen.getByLabelText(`File size limit unit`)
|
|
expect(sizeLimitUnitSelect).toHaveTextContent(`MB`)
|
|
await userEvent.click(sizeLimitUnitSelect)
|
|
const bytesOption = screen.getByRole(`option`, { name: `bytes` })
|
|
await userEvent.click(bytesOption)
|
|
expect(sizeLimitUnitSelect).toHaveTextContent(`bytes`)
|
|
|
|
const mimeTypeToggle = screen.getByLabelText(`Restrict MIME types`)
|
|
expect(mimeTypeToggle).not.toBeChecked()
|
|
await userEvent.click(mimeTypeToggle)
|
|
expect(mimeTypeToggle).toBeChecked()
|
|
|
|
const mimeTypeInput = screen.getByLabelText(`Allowed MIME types`)
|
|
expect(mimeTypeInput).toHaveValue(``)
|
|
await userEvent.type(mimeTypeInput, `image/jpeg, image/png`)
|
|
|
|
const submitButton = screen.getByRole(`button`, { name: `Create` })
|
|
|
|
fireEvent.click(submitButton)
|
|
})
|
|
|
|
it('keeps the bucket it created when the retention policy is rejected', async () => {
|
|
versioningEnabled.current = true
|
|
const postBucket = vi.fn(() => Response.json({ name: 'versioned' }))
|
|
addAPIMock({ method: 'post', path: '/platform/storage/:ref/buckets', response: postBucket })
|
|
addAPIMock({
|
|
method: 'put',
|
|
path: '/platform/storage/:ref/buckets/:id/lifecycle',
|
|
response: () => Response.json({ message: 'Lifecycle is not available' }, { status: 500 }),
|
|
})
|
|
const onOpenChange = vi.fn()
|
|
|
|
customRender(
|
|
<ProjectContextProvider projectRef="default">
|
|
<CreateBucketModal open onOpenChange={onOpenChange} />
|
|
</ProjectContextProvider>
|
|
)
|
|
|
|
await userEvent.type(await screen.findByLabelText('Bucket name'), 'versioned')
|
|
await userEvent.click(screen.getByRole('switch', { name: 'Object versioning' }))
|
|
|
|
// Submitted off the form itself: jsdom doesn't associate a button with a form by
|
|
// attribute, so clicking the footer's Create submits nothing.
|
|
fireEvent.submit(document.getElementById('create-storage-bucket-form')!)
|
|
|
|
// The bucket exists, so the modal closes rather than inviting a retry that would
|
|
// only collide on the name.
|
|
await waitFor(() => expect(onOpenChange).toHaveBeenCalledWith(false))
|
|
expect(postBucket).toHaveBeenCalledTimes(1)
|
|
})
|
|
})
|