Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/backend/jest-setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import 'reflect-metadata';
3 changes: 2 additions & 1 deletion apps/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
"testEnvironment": "node",
"setupFiles": ["../jest-setup.js"]
}
}
77 changes: 77 additions & 0 deletions apps/backend/src/modules/admin/dto/consistency-check.dto.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { validate } from 'class-validator';
import {
ConsistencyCheckByIdsDto,
ConsistencyCheckByRangeDto,
} from './consistency-check.dto';

describe('ConsistencyCheck DTOs', () => {
describe('ConsistencyCheckByIdsDto', () => {
it('should validate with valid escrow IDs', async () => {
const dto = new ConsistencyCheckByIdsDto();
dto.escrowIds = [1, 2, 3, 4, 5];
const errors = await validate(dto);
expect(errors).toHaveLength(0);
});

it('should reject non-array input', async () => {
const dto = new ConsistencyCheckByIdsDto();
dto.escrowIds = [] as any;
const errors = await validate(dto);
expect(errors).toHaveLength(0); // Empty array is valid
});

it('should reject non-number values in array', async () => {
const dto = new ConsistencyCheckByIdsDto();
dto.escrowIds = [1, 2, 'invalid' as any];
const errors = await validate(dto);
expect(errors.length).toBeGreaterThan(0);
});

it('should reject zero or negative IDs', async () => {
const dto = new ConsistencyCheckByIdsDto();
dto.escrowIds = [0, -1, 1];
const errors = await validate(dto);
expect(errors.length).toBeGreaterThan(0);
});
});

describe('ConsistencyCheckByRangeDto', () => {
it('should validate with valid range', async () => {
const dto = new ConsistencyCheckByRangeDto();
dto.fromId = 1;
dto.toId = 100;
const errors = await validate(dto);
expect(errors).toHaveLength(0);
});

it('should reject missing fromId', async () => {
const dto = new ConsistencyCheckByRangeDto();
dto.toId = 100;
const errors = await validate(dto);
expect(errors.length).toBeGreaterThan(0);
});

it('should reject missing toId', async () => {
const dto = new ConsistencyCheckByRangeDto();
dto.fromId = 1;
const errors = await validate(dto);
expect(errors.length).toBeGreaterThan(0);
});

it('should reject zero fromId', async () => {
const dto = new ConsistencyCheckByRangeDto();
dto.fromId = 0;
dto.toId = 100;
const errors = await validate(dto);
expect(errors.length).toBeGreaterThan(0);
});

it('should reject negative toId', async () => {
const dto = new ConsistencyCheckByRangeDto();
dto.fromId = 1;
dto.toId = -10;
const errors = await validate(dto);
expect(errors.length).toBeGreaterThan(0);
});
});
});
26 changes: 23 additions & 3 deletions apps/backend/src/modules/admin/dto/consistency-check.dto.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,28 @@
// DTOs and types for the Consistency Checker feature
import { IsArray, IsNumber, Min } from 'class-validator';
import { Type } from 'class-transformer';

export class ConsistencyCheckByIdsDto {
@IsArray()
@IsNumber({}, { each: true })
@Min(1, { each: true })
@Type(() => Number)
escrowIds: number[];
}

export class ConsistencyCheckByRangeDto {
@IsNumber()
@Min(1)
fromId: number;

@IsNumber()
@Min(1)
toId: number;
}

// Union type for request validation
export type ConsistencyCheckRequest =
| { escrowIds: number[] }
| { fromId: number; toId: number };
| ConsistencyCheckByIdsDto
| ConsistencyCheckByRangeDto;

export interface FieldMismatch {
fieldName: string;
Expand Down
153 changes: 153 additions & 0 deletions apps/backend/src/modules/assets/dto/asset.dto.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import { validate } from 'class-validator';
import { CreateAssetDto, UpdateAssetDto } from './asset.dto';

describe('Asset DTOs', () => {
describe('CreateAssetDto', () => {
it('should validate with all required fields', async () => {
const dto = new CreateAssetDto();
dto.code = 'XLM';
dto.displayName = 'Stellar Lumens';
const errors = await validate(dto);
expect(errors).toHaveLength(0);
});

it('should validate with optional issuer', async () => {
const dto = new CreateAssetDto();
dto.code = 'USD';
dto.issuer = 'GD5JDQXKEVPR7QD2R7LXKXN7M4ZGAPYI7F7DQ7K7D7D7D7D7D7D7DABC';
dto.displayName = 'US Dollar';
const errors = await validate(dto);
expect(errors).toHaveLength(0);
});

it('should reject invalid issuer (not Stellar address)', async () => {
const dto = new CreateAssetDto();
dto.code = 'USD';
dto.issuer = 'invalid';
dto.displayName = 'US Dollar';
const errors = await validate(dto);
expect(errors.length).toBeGreaterThan(0);
});

it('should reject missing code', async () => {
const dto = new CreateAssetDto();
dto.displayName = 'Stellar Lumens';
const errors = await validate(dto);
expect(errors.length).toBeGreaterThan(0);
});

it('should reject missing displayName', async () => {
const dto = new CreateAssetDto();
dto.code = 'XLM';
const errors = await validate(dto);
expect(errors.length).toBeGreaterThan(0);
});

it('should reject code exceeding max length', async () => {
const dto = new CreateAssetDto();
dto.code = 'A'.repeat(13);
dto.displayName = 'Test';
const errors = await validate(dto);
expect(errors.length).toBeGreaterThan(0);
});

it('should reject displayName exceeding max length', async () => {
const dto = new CreateAssetDto();
dto.code = 'XLM';
dto.displayName = 'A'.repeat(256);
const errors = await validate(dto);
expect(errors.length).toBeGreaterThan(0);
});

it('should reject iconUrl exceeding max length', async () => {
const dto = new CreateAssetDto();
dto.code = 'XLM';
dto.displayName = 'Test';
dto.iconUrl = 'A'.repeat(501);
const errors = await validate(dto);
expect(errors.length).toBeGreaterThan(0);
});

it('should reject empty iconUrl when provided', async () => {
const dto = new CreateAssetDto();
dto.code = 'XLM';
dto.displayName = 'Test';
dto.iconUrl = '';
const errors = await validate(dto);
expect(errors.length).toBeGreaterThan(0);
});

it('should reject decimals outside range', async () => {
const dto = new CreateAssetDto();
dto.code = 'XLM';
dto.displayName = 'Test';
dto.decimals = 19;
const errors = await validate(dto);
expect(errors.length).toBeGreaterThan(0);
});
});

describe('UpdateAssetDto', () => {
it('should validate with no fields (all optional)', async () => {
const dto = new UpdateAssetDto();
const errors = await validate(dto);
expect(errors).toHaveLength(0);
});

it('should validate with partial updates', async () => {
const dto = new UpdateAssetDto();
dto.displayName = 'Updated Name';
const errors = await validate(dto);
expect(errors).toHaveLength(0);
});

it('should reject invalid issuer', async () => {
const dto = new UpdateAssetDto();
dto.issuer = 'invalid';
const errors = await validate(dto);
expect(errors.length).toBeGreaterThan(0);
});

it('should reject code exceeding max length', async () => {
const dto = new UpdateAssetDto();
dto.code = 'A'.repeat(13);
const errors = await validate(dto);
expect(errors.length).toBeGreaterThan(0);
});

it('should reject empty code when provided', async () => {
const dto = new UpdateAssetDto();
dto.code = '';
const errors = await validate(dto);
expect(errors.length).toBeGreaterThan(0);
});

it('should reject empty displayName when provided', async () => {
const dto = new UpdateAssetDto();
dto.displayName = '';
const errors = await validate(dto);
expect(errors.length).toBeGreaterThan(0);
});

it('should reject displayName exceeding max length', async () => {
const dto = new UpdateAssetDto();
dto.displayName = 'A'.repeat(256);
const errors = await validate(dto);
expect(errors.length).toBeGreaterThan(0);
});

it('should reject empty iconUrl when provided', async () => {
const dto = new UpdateAssetDto();
dto.iconUrl = '';
const errors = await validate(dto);
expect(errors.length).toBeGreaterThan(0);
});

it('should reject iconUrl exceeding max length', async () => {
const dto = new UpdateAssetDto();
dto.iconUrl = 'A'.repeat(501);
const errors = await validate(dto);
expect(errors.length).toBeGreaterThan(0);
});
});
});
16 changes: 12 additions & 4 deletions apps/backend/src/modules/assets/dto/asset.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,29 @@ import {
Max,
IsBoolean,
Length,
MaxLength,
} from 'class-validator';
import { IsStellarAddress } from '../../../utils/validators';

export class CreateAssetDto {
@IsString()
@IsNotEmpty()
@Length(1, 12)
code: string;

@IsString()
@IsStellarAddress()
@IsOptional()
@Length(56, 56)
issuer?: string;

@IsString()
@IsNotEmpty()
@MaxLength(255)
displayName: string;

@IsString()
@IsNotEmpty()
@IsOptional()
@MaxLength(500)
iconUrl?: string;

@IsInt()
Expand All @@ -41,21 +45,25 @@ export class CreateAssetDto {

export class UpdateAssetDto {
@IsString()
@IsNotEmpty()
@IsOptional()
@Length(1, 12)
code?: string;

@IsString()
@IsStellarAddress()
@IsOptional()
@Length(56, 56)
issuer?: string;

@IsString()
@IsNotEmpty()
@IsOptional()
@MaxLength(255)
displayName?: string;

@IsString()
@IsNotEmpty()
@IsOptional()
@MaxLength(500)
iconUrl?: string;

@IsInt()
Expand Down
Loading