mirror of
https://github.com/supabase/supabase.git
synced 2026-10-11 04:15:04 +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 | ## [4/10] Storage object versioning: object versions data layer **Base:** `feat/storage-versioning/003-bucket-modals` (PR 3) ### This PR The query and mutation hooks for the version history UI, written to `queryOptions` using the real Storage endpoints. - `object-versions-query.ts` — the version list, plus `ObjectVersion` and `LifecyclePolicy` - `object-version-restore-mutation.ts` — promote a noncurrent version to current - `object-version-delete-mutation.ts` — remove one specific version - `object-purge-mutation.ts` — delete an object and every version, bypassing versioning - `VersionHistory.utils.ts` — `computeVersionFate`, the pure rule deciding what removal outlook each version row shows - `BroomSparklesIcon.tsx` — inline SVG for a glyph absent from lucide-react 0.436 Easier to test directly from next PR in the stack #49208 which wires the queries to the real file preview panel ui. --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
94 lines
3.4 KiB
TypeScript
94 lines
3.4 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
|
|
import { toObjectVersions } from './object-versions-query'
|
|
|
|
type StorageObject = Parameters<typeof toObjectVersions>[0][number]
|
|
|
|
const object = (overrides: Partial<StorageObject>): StorageObject => ({
|
|
name: 'folder/photo.jpg',
|
|
id: null,
|
|
updated_at: null,
|
|
created_at: null,
|
|
last_accessed_at: null,
|
|
metadata: { size: 100 },
|
|
...overrides,
|
|
})
|
|
|
|
describe('toObjectVersions', () => {
|
|
it('returns newest first', () => {
|
|
const versions = toObjectVersions([
|
|
object({ version: 'old', created_at: '2026-01-01T00:00:00Z', archived_at: '2026-02-01' }),
|
|
object({ version: 'new', created_at: '2026-03-01T00:00:00Z' }),
|
|
object({ version: 'mid', created_at: '2026-02-01T00:00:00Z', archived_at: '2026-03-01' }),
|
|
])
|
|
|
|
expect(versions.map((version) => version.versionId)).toEqual(['new', 'mid', 'old'])
|
|
})
|
|
|
|
it('treats a row with no archived_at as the current version', () => {
|
|
const versions = toObjectVersions([
|
|
object({ version: 'a', created_at: '2026-01-01T00:00:00Z' }),
|
|
object({ version: 'b', created_at: '2026-02-01T00:00:00Z', archived_at: '2026-03-01' }),
|
|
])
|
|
|
|
expect(versions.find((version) => version.versionId === 'a')?.isCurrent).toBe(true)
|
|
expect(versions.find((version) => version.versionId === 'b')?.isCurrent).toBe(false)
|
|
})
|
|
|
|
it('labels the oldest upload and treats the rest as overwrites', () => {
|
|
const versions = toObjectVersions([
|
|
object({ version: 'a', created_at: '2026-01-01T00:00:00Z' }),
|
|
object({ version: 'b', created_at: '2026-02-01T00:00:00Z' }),
|
|
object({ version: 'c', created_at: '2026-03-01T00:00:00Z' }),
|
|
])
|
|
|
|
expect(versions.map((version) => version.action)).toEqual([
|
|
'overwrite',
|
|
'overwrite',
|
|
'initial upload',
|
|
])
|
|
})
|
|
|
|
it('labels delete markers and does not count them as the initial upload', () => {
|
|
const versions = toObjectVersions([
|
|
object({ version: 'upload', created_at: '2026-01-01T00:00:00Z' }),
|
|
object({
|
|
version: 'marker',
|
|
created_at: '2026-02-01T00:00:00Z',
|
|
is_delete_marker: true,
|
|
metadata: null,
|
|
}),
|
|
])
|
|
|
|
expect(versions.map((version) => version.action)).toEqual(['delete marker', 'initial upload'])
|
|
})
|
|
|
|
it('reads the size off the metadata, defaulting to zero', () => {
|
|
const versions = toObjectVersions([
|
|
object({ version: 'a', created_at: '2026-01-01T00:00:00Z', metadata: { size: 2048 } }),
|
|
object({ version: 'b', created_at: '2026-02-01T00:00:00Z', metadata: null }),
|
|
])
|
|
|
|
expect(versions.find((version) => version.versionId === 'a')?.size).toBe(2048)
|
|
expect(versions.find((version) => version.versionId === 'b')?.size).toBe(0)
|
|
})
|
|
|
|
it('keeps each version its own mime type, since an overwrite can change it', () => {
|
|
const versions = toObjectVersions([
|
|
object({
|
|
version: 'a',
|
|
created_at: '2026-01-01T00:00:00Z',
|
|
metadata: { mimetype: 'image/png' },
|
|
}),
|
|
object({ version: 'b', created_at: '2026-02-01T00:00:00Z', metadata: { size: 1 } }),
|
|
])
|
|
|
|
expect(versions.find((version) => version.versionId === 'a')?.mimeType).toBe('image/png')
|
|
expect(versions.find((version) => version.versionId === 'b')?.mimeType).toBeUndefined()
|
|
})
|
|
|
|
it('drops rows the API returned without a version id', () => {
|
|
expect(toObjectVersions([object({ created_at: '2026-01-01T00:00:00Z' })])).toEqual([])
|
|
})
|
|
})
|