feat: add Husky hooks and fix provider tutorial UI/logic

- Add Husky pre-commit and pre-push hooks for linting, type checking, and build validation
- Fix visual hierarchy bug in the provider onboarding tutorial
- Remove feedback modal
- Move MinIO bucket creation logic to before app startup
- Wire MiniMax audio through voice generation pipeline
- Fix scene insertion issues
- Fix portal tutorial modal and harden panel variant task flow
This commit is contained in:
saturn
2026-03-09 02:42:35 +08:00
parent 881ed44996
commit fba480ae6e
34 changed files with 721 additions and 247 deletions

View File

@@ -0,0 +1,97 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { ensureStorageReady } from '@/lib/storage/bootstrap'
type MockCommand = {
readonly type: 'HeadBucketCommand' | 'CreateBucketCommand'
readonly input: Record<string, unknown>
}
const {
sendMock,
s3ClientMock,
headBucketCommandMock,
createBucketCommandMock,
} = vi.hoisted(() => ({
sendMock: vi.fn<(command: MockCommand) => Promise<unknown>>(),
s3ClientMock: vi.fn(() => ({ send: undefined as unknown })),
headBucketCommandMock: vi.fn((input: Record<string, unknown>): MockCommand => ({
type: 'HeadBucketCommand',
input,
})),
createBucketCommandMock: vi.fn((input: Record<string, unknown>): MockCommand => ({
type: 'CreateBucketCommand',
input,
})),
}))
s3ClientMock.mockImplementation(() => ({ send: sendMock }))
vi.mock('@aws-sdk/client-s3', () => ({
S3Client: s3ClientMock,
HeadBucketCommand: headBucketCommandMock,
CreateBucketCommand: createBucketCommandMock,
}))
describe('storage bootstrap', () => {
beforeEach(() => {
vi.clearAllMocks()
process.env.MINIO_ENDPOINT = 'http://127.0.0.1:9000'
process.env.MINIO_REGION = 'us-east-1'
process.env.MINIO_BUCKET = 'waoowaoo'
process.env.MINIO_ACCESS_KEY = 'minioadmin'
process.env.MINIO_SECRET_KEY = 'minioadmin'
process.env.MINIO_FORCE_PATH_STYLE = 'true'
})
it('skips startup storage initialization when STORAGE_TYPE=local', async () => {
await expect(ensureStorageReady({ storageType: 'local' })).resolves.toBe('skipped')
expect(s3ClientMock).not.toHaveBeenCalled()
})
it('verifies the MinIO bucket during startup when it already exists', async () => {
sendMock.mockResolvedValueOnce({})
await expect(ensureStorageReady({ storageType: 'minio' })).resolves.toBe('existing')
expect(s3ClientMock).toHaveBeenCalledWith({
endpoint: 'http://127.0.0.1:9000',
region: 'us-east-1',
forcePathStyle: true,
credentials: {
accessKeyId: 'minioadmin',
secretAccessKey: 'minioadmin',
},
})
expect(headBucketCommandMock).toHaveBeenCalledWith({ Bucket: 'waoowaoo' })
expect(createBucketCommandMock).not.toHaveBeenCalled()
})
it('creates the MinIO bucket during startup when HeadBucket reports it missing', async () => {
sendMock
.mockRejectedValueOnce(Object.assign(new Error('missing bucket'), {
name: 'NotFound',
$metadata: { httpStatusCode: 404 },
}))
.mockResolvedValueOnce({})
await expect(ensureStorageReady({ storageType: 'minio' })).resolves.toBe('created')
expect(headBucketCommandMock).toHaveBeenCalledWith({ Bucket: 'waoowaoo' })
expect(createBucketCommandMock).toHaveBeenCalledWith({ Bucket: 'waoowaoo' })
expect(sendMock).toHaveBeenNthCalledWith(2, {
type: 'CreateBucketCommand',
input: { Bucket: 'waoowaoo' },
})
})
it('fails fast when MinIO returns a non-bucket-missing error at startup', async () => {
const startupError = Object.assign(new Error('MinIO unavailable'), {
name: 'TimeoutError',
$metadata: { httpStatusCode: 503 },
})
sendMock.mockRejectedValueOnce(startupError)
await expect(ensureStorageReady({ storageType: 'minio' })).rejects.toBe(startupError)
expect(createBucketCommandMock).not.toHaveBeenCalled()
})
})