Enhance branch management with foreign key relation handling
- Updated `BranchesModule` to include foreign key relations in list and write responses, ensuring they are represented as nested objects using `pickRelation`. - Introduced new `relation-response.mdc` file to define guidelines for embedding foreign key relations. - Modified `BranchesRepository` to support fetching related `division`, `createdByUser`, and `updatedByUser` data. - Updated DTOs and service methods to reflect changes in response structure, removing direct foreign key IDs. - Added unit tests to validate the new relation handling in branches service and repository. - Enhanced e2e tests to verify the correct structure of branch responses with nested relations.
This commit is contained in:
@@ -22,3 +22,13 @@ export {
|
||||
PAGINATION_DEFAULT_LIMIT,
|
||||
PAGINATION_MAX_LIMIT,
|
||||
} from './pagination.constants';
|
||||
export {
|
||||
DEFAULT_RELATION_FIELDS,
|
||||
pickDefaultRelation,
|
||||
pickRelation,
|
||||
pickUserRelation,
|
||||
USER_RELATION_FIELDS,
|
||||
type DefaultRelation,
|
||||
type UserRelation,
|
||||
} from './relation-fields';
|
||||
export { DefaultRelationDto, UserRelationDto } from './relation.dto';
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import {
|
||||
DEFAULT_RELATION_FIELDS,
|
||||
pickDefaultRelation,
|
||||
pickRelation,
|
||||
pickUserRelation,
|
||||
USER_RELATION_FIELDS,
|
||||
} from './relation-fields';
|
||||
|
||||
describe('pickRelation', () => {
|
||||
const catalog = {
|
||||
id: 'div-1',
|
||||
code: 'JKT',
|
||||
name: 'Jakarta',
|
||||
status: 'active',
|
||||
extra: 'secret',
|
||||
};
|
||||
|
||||
const user = {
|
||||
id: 'user-1',
|
||||
username: 'admin',
|
||||
passwordHash: 'hashed',
|
||||
};
|
||||
|
||||
it('picks default id, code, name fields', () => {
|
||||
expect(pickRelation(catalog, DEFAULT_RELATION_FIELDS)).toEqual({
|
||||
id: 'div-1',
|
||||
code: 'JKT',
|
||||
name: 'Jakarta',
|
||||
});
|
||||
});
|
||||
|
||||
it('picks a custom field list for users', () => {
|
||||
expect(pickRelation(user, USER_RELATION_FIELDS)).toEqual({
|
||||
id: 'user-1',
|
||||
username: 'admin',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns null for null or undefined sources', () => {
|
||||
const missing: typeof catalog | null = null;
|
||||
const unset: typeof catalog | undefined = undefined;
|
||||
expect(
|
||||
pickRelation<typeof catalog, (typeof DEFAULT_RELATION_FIELDS)[number]>(
|
||||
missing,
|
||||
DEFAULT_RELATION_FIELDS,
|
||||
),
|
||||
).toBeNull();
|
||||
expect(
|
||||
pickRelation<typeof catalog, (typeof DEFAULT_RELATION_FIELDS)[number]>(
|
||||
unset,
|
||||
DEFAULT_RELATION_FIELDS,
|
||||
),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('does not copy fields outside the list', () => {
|
||||
const picked = pickRelation(catalog, DEFAULT_RELATION_FIELDS);
|
||||
expect(picked).not.toHaveProperty('status');
|
||||
expect(picked).not.toHaveProperty('extra');
|
||||
});
|
||||
});
|
||||
|
||||
describe('pickDefaultRelation / pickUserRelation', () => {
|
||||
it('maps catalog and user sources without leaking extra fields', () => {
|
||||
expect(
|
||||
pickDefaultRelation({
|
||||
id: 'div-1',
|
||||
code: 'JKT',
|
||||
name: 'Jakarta',
|
||||
}),
|
||||
).toEqual({ id: 'div-1', code: 'JKT', name: 'Jakarta' });
|
||||
expect(pickDefaultRelation(null)).toBeNull();
|
||||
expect(pickUserRelation({ id: 'user-1', username: 'admin' })).toEqual({
|
||||
id: 'user-1',
|
||||
username: 'admin',
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
export const DEFAULT_RELATION_FIELDS = ['id', 'code', 'name'] as const;
|
||||
export const USER_RELATION_FIELDS = ['id', 'username'] as const;
|
||||
|
||||
export type DefaultRelation = {
|
||||
readonly id: string;
|
||||
readonly code: string;
|
||||
readonly name: string;
|
||||
};
|
||||
|
||||
export type UserRelation = {
|
||||
readonly id: string;
|
||||
readonly username: string;
|
||||
};
|
||||
|
||||
export function pickRelation<T, K extends keyof NonNullable<T>>(
|
||||
source: T | null | undefined,
|
||||
fields: readonly K[],
|
||||
): Pick<NonNullable<T>, K> | null {
|
||||
if (source == null) {
|
||||
return null;
|
||||
}
|
||||
const result = {} as Pick<NonNullable<T>, K>;
|
||||
for (const field of fields) {
|
||||
result[field] = source[field];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function pickDefaultRelation(
|
||||
source: DefaultRelation | null | undefined,
|
||||
): DefaultRelation | null {
|
||||
return pickRelation(source, DEFAULT_RELATION_FIELDS);
|
||||
}
|
||||
|
||||
export function pickUserRelation(source: UserRelation): UserRelation {
|
||||
return (
|
||||
pickRelation(source, USER_RELATION_FIELDS) ?? {
|
||||
id: source.id,
|
||||
username: source.username,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class DefaultRelationDto {
|
||||
@ApiProperty({ format: 'uuid' })
|
||||
id!: string;
|
||||
|
||||
@ApiProperty()
|
||||
code!: string;
|
||||
|
||||
@ApiProperty()
|
||||
name!: string;
|
||||
}
|
||||
|
||||
export class UserRelationDto {
|
||||
@ApiProperty({ format: 'uuid' })
|
||||
id!: string;
|
||||
|
||||
@ApiProperty()
|
||||
username!: string;
|
||||
}
|
||||
Reference in New Issue
Block a user