mirror of
https://github.com/immich-app/immich
synced 2025-11-07 17:27:20 +00:00
fix: invalid storage quota with decimals (#21271)
This commit is contained in:
parent
5405810a38
commit
70e59c00d5
4 changed files with 84 additions and 4 deletions
79
server/src/controllers/user-admin.controller.spec.ts
Normal file
79
server/src/controllers/user-admin.controller.spec.ts
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
import { UserAdminController } from 'src/controllers/user-admin.controller';
|
||||||
|
import { UserAdminCreateDto } from 'src/dtos/user.dto';
|
||||||
|
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||||
|
import { UserAdminService } from 'src/services/user-admin.service';
|
||||||
|
import request from 'supertest';
|
||||||
|
import { errorDto } from 'test/medium/responses';
|
||||||
|
import { factory } from 'test/small.factory';
|
||||||
|
import { automock, ControllerContext, controllerSetup, mockBaseService } from 'test/utils';
|
||||||
|
|
||||||
|
describe(UserAdminController.name, () => {
|
||||||
|
let ctx: ControllerContext;
|
||||||
|
const service = mockBaseService(UserAdminService);
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
ctx = await controllerSetup(UserAdminController, [
|
||||||
|
{ provide: LoggingRepository, useValue: automock(LoggingRepository, { strict: false }) },
|
||||||
|
{ provide: UserAdminService, useValue: service },
|
||||||
|
]);
|
||||||
|
return () => ctx.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
service.resetAllMocks();
|
||||||
|
ctx.reset();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('GET /admin/users', () => {
|
||||||
|
it('should be an authenticated route', async () => {
|
||||||
|
await request(ctx.getHttpServer()).get('/admin/users');
|
||||||
|
expect(ctx.authenticate).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('POST /admin/users', () => {
|
||||||
|
it('should be an authenticated route', async () => {
|
||||||
|
await request(ctx.getHttpServer()).post('/admin/users');
|
||||||
|
expect(ctx.authenticate).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should not allow decimal quota`, async () => {
|
||||||
|
const dto: UserAdminCreateDto = {
|
||||||
|
email: 'user@immich.app',
|
||||||
|
password: 'test',
|
||||||
|
name: 'Test User',
|
||||||
|
quotaSizeInBytes: 1.2,
|
||||||
|
};
|
||||||
|
|
||||||
|
const { status, body } = await request(ctx.getHttpServer())
|
||||||
|
.post(`/admin/users`)
|
||||||
|
.set('Authorization', `Bearer token`)
|
||||||
|
.send(dto);
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorDto.badRequest(expect.arrayContaining(['quotaSizeInBytes must be an integer number'])));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('GET /admin/users/:id', () => {
|
||||||
|
it('should be an authenticated route', async () => {
|
||||||
|
await request(ctx.getHttpServer()).get(`/admin/users/${factory.uuid()}`);
|
||||||
|
expect(ctx.authenticate).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('PUT /admin/users/:id', () => {
|
||||||
|
it('should be an authenticated route', async () => {
|
||||||
|
await request(ctx.getHttpServer()).put(`/admin/users/${factory.uuid()}`);
|
||||||
|
expect(ctx.authenticate).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should not allow decimal quota`, async () => {
|
||||||
|
const { status, body } = await request(ctx.getHttpServer())
|
||||||
|
.put(`/admin/users/${factory.uuid()}`)
|
||||||
|
.set('Authorization', `Bearer token`)
|
||||||
|
.send({ quotaSizeInBytes: 1.2 });
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorDto.badRequest(expect.arrayContaining(['quotaSizeInBytes must be an integer number'])));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
import { Transform } from 'class-transformer';
|
import { Transform } from 'class-transformer';
|
||||||
import { IsEmail, IsNotEmpty, IsNumber, IsString, Min } from 'class-validator';
|
import { IsEmail, IsInt, IsNotEmpty, IsString, Min } from 'class-validator';
|
||||||
import { User, UserAdmin } from 'src/database';
|
import { User, UserAdmin } from 'src/database';
|
||||||
import { UserAvatarColor, UserMetadataKey, UserStatus } from 'src/enum';
|
import { UserAvatarColor, UserMetadataKey, UserStatus } from 'src/enum';
|
||||||
import { UserMetadataItem } from 'src/types';
|
import { UserMetadataItem } from 'src/types';
|
||||||
|
|
@ -91,7 +91,7 @@ export class UserAdminCreateDto {
|
||||||
storageLabel?: string | null;
|
storageLabel?: string | null;
|
||||||
|
|
||||||
@Optional({ nullable: true })
|
@Optional({ nullable: true })
|
||||||
@IsNumber()
|
@IsInt()
|
||||||
@Min(0)
|
@Min(0)
|
||||||
@ApiProperty({ type: 'integer', format: 'int64' })
|
@ApiProperty({ type: 'integer', format: 'int64' })
|
||||||
quotaSizeInBytes?: number | null;
|
quotaSizeInBytes?: number | null;
|
||||||
|
|
@ -137,7 +137,7 @@ export class UserAdminUpdateDto {
|
||||||
shouldChangePassword?: boolean;
|
shouldChangePassword?: boolean;
|
||||||
|
|
||||||
@Optional({ nullable: true })
|
@Optional({ nullable: true })
|
||||||
@IsNumber()
|
@IsInt()
|
||||||
@Min(0)
|
@Min(0)
|
||||||
@ApiProperty({ type: 'integer', format: 'int64' })
|
@ApiProperty({ type: 'integer', format: 'int64' })
|
||||||
quotaSizeInBytes?: number | null;
|
quotaSizeInBytes?: number | null;
|
||||||
|
|
|
||||||
|
|
@ -122,7 +122,7 @@
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
<Field label={$t('admin.quota_size_gib')}>
|
<Field label={$t('admin.quota_size_gib')}>
|
||||||
<Input bind:value={quotaSize} type="number" placeholder={$t('unlimited')} min="0" />
|
<Input bind:value={quotaSize} type="number" placeholder={$t('unlimited')} min="0" step="1" />
|
||||||
{#if quotaSizeWarning}
|
{#if quotaSizeWarning}
|
||||||
<HelperText color="danger">{$t('errors.quota_higher_than_disk_size')}</HelperText>
|
<HelperText color="danger">{$t('errors.quota_higher_than_disk_size')}</HelperText>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,7 @@
|
||||||
name="quotaSize"
|
name="quotaSize"
|
||||||
placeholder={$t('unlimited')}
|
placeholder={$t('unlimited')}
|
||||||
type="number"
|
type="number"
|
||||||
|
step="1"
|
||||||
min="0"
|
min="0"
|
||||||
bind:value={quotaSize}
|
bind:value={quotaSize}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue