diff --git a/src/instructor/instructor.service.ts b/src/instructor/instructor.service.ts index cebb4d5..2481597 100644 --- a/src/instructor/instructor.service.ts +++ b/src/instructor/instructor.service.ts @@ -2178,9 +2178,7 @@ export class InstructorService { organizationId, organizationName, ), - displayName: { - startsWith: baseName, - }, + platformId: masterPlatform.id, }, select: { displayName: true, @@ -2227,7 +2225,11 @@ export class InstructorService { }); } catch (error) { if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === 'P2002') { - throw new ConflictException('Platform name already exists'); + const target = error.meta?.target; + if (Array.isArray(target) && target.includes('instance_no')) { + throw new ConflictException('플랫폼 등록번호가 중복되었습니다. 다시 등록해 주세요.'); + } + throw new ConflictException('이미 사용 중인 플랫폼 이름입니다. 다른 이름을 입력해 주세요.'); } throw error; } diff --git a/src/instructor/platform-registration.spec.ts b/src/instructor/platform-registration.spec.ts new file mode 100644 index 0000000..3ed41da --- /dev/null +++ b/src/instructor/platform-registration.spec.ts @@ -0,0 +1,57 @@ +import { Prisma } from '@prisma/client'; +import { InstructorService } from './instructor.service'; + +describe('platform registration', () => { + const input = { platformId: 'yahboom', agentNodeId: 'node-2', agentHostname: 'car-2' }; + function setup() { + const registered = [ + { platformId: 'yahboom', displayName: 'Custom car', instanceNo: 1 }, + { platformId: 'mkmini', displayName: 'Yahboom MicroROS-Car-Pi5 #02', instanceNo: 2 }, + ]; + const prisma = { + platform: { findFirst: jest.fn().mockResolvedValue({ id: 'yahboom', displayName: 'Yahboom MicroROS-Car-Pi5' }) }, + instructorPlatform: { + findMany: jest.fn(async ({ where }) => registered.filter(row => + (!where.platformId || row.platformId === where.platformId) + && (!where.displayName?.startsWith || row.displayName.startsWith(where.displayName.startsWith)))), + create: jest.fn().mockResolvedValue({}), + }, + }; + const service = new InstructorService({} as never, {} as never, prisma as never, {} as never, {} as never, {} as never); + jest.spyOn(service as any, 'getInstructor').mockResolvedValue({ + authUser: { primaryAffiliation: { organizationId: 'org-id', organizationName: 'org' } }, + }); + jest.spyOn(service, 'listPlatforms').mockResolvedValue({ platforms: [] } as never); + return { service, prisma }; + } + + it('counts renamed vehicles by platform ID, ignoring names of other types', async () => { + const { service, prisma } = setup(); + await service.createPlatform('token', input); + expect(prisma.instructorPlatform.findMany).toHaveBeenCalledWith(expect.objectContaining({ + where: expect.objectContaining({ platformId: 'yahboom', OR: expect.any(Array) }), + })); + expect(prisma.instructorPlatform.create).toHaveBeenCalledWith({ + data: expect.objectContaining({ instanceNo: 2, displayName: 'Yahboom MicroROS-Car-Pi5 #02' }), + }); + }); + + it('keeps a user-provided name while assigning the next internal number', async () => { + const { service, prisma } = setup(); + await service.createPlatform('token', { ...input, displayName: 'Second car' }); + expect(prisma.instructorPlatform.create).toHaveBeenCalledWith({ + data: expect.objectContaining({ instanceNo: 2, displayName: 'Second car' }), + }); + }); + + it.each([ + [['organization_name', 'display_name'], '이미 사용 중인 플랫폼 이름'], + [['organization_name', 'platform_id', 'instance_no'], '플랫폼 등록번호가 중복'], + ])('distinguishes unique constraint failures for %j', async (target, message) => { + const { service, prisma } = setup(); + prisma.instructorPlatform.create.mockRejectedValue(new Prisma.PrismaClientKnownRequestError('duplicate', { + code: 'P2002', clientVersion: '6', meta: { target }, + })); + await expect(service.createPlatform('token', input)).rejects.toThrow(message); + }); +});