Skip to content
Merged
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
2 changes: 1 addition & 1 deletion src/features/auth/auth-admin-account.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ type AdminAccountConnection {
"""
input AdminCreateAdminInput {
"""
로그인 username. 4~80자, 소문자·숫자·`.`·`_`·`-`만 허용. 이미 쓰이고 있으면 BAD_USER_INPUT.
로그인 username. 4~80자, 영문 대소문자·숫자·`.`·`_`·`-`만 허용. 이미 쓰이고 있으면(대소문자만 달라도) BAD_USER_INPUT.
"""
username: String!
"""
Expand Down
4 changes: 2 additions & 2 deletions src/features/auth/constants/auth-admin.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

export const MIN_USERNAME_LENGTH = 4;
export const MAX_USERNAME_LENGTH = 80;
/** 정책: 소문자·숫자·`.`·`_`·`-`만. 대소문자 혼용 username 충돌을 원천 차단한다. */
export const USERNAME_PATTERN = /^[a-z0-9._-]+$/;
/** 정책: 영문 대소문자·숫자·`.`·`_`·`-`만. 대소문자만 다른 username은 컬럼 정렬(ci)이 같은 값으로 봐 중복으로 막는다. */
export const USERNAME_PATTERN = /^[A-Za-z0-9._-]+$/;

export const MAX_EMAIL_LENGTH = 320;
export const MAX_ACCOUNT_NAME_LENGTH = 100;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,15 @@ describe('AdminCreateAdminInput', () => {
).toHaveLength(0);
});

it('username에 영문 대문자를 허용한다', async () => {
expect(
await validate(build({ ...valid, username: 'Ops.Admin_1' })),
).toHaveLength(0);
});

it.each([
['길이 4 미만', 'abc'],
['길이 80 초과', 'a'.repeat(81)],
['대문자 포함', 'Admin1'],
['공백 포함', 'ad min'],
['허용 외 문자(@)', 'ad@min'],
['한글', '관리자계정'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,18 @@ describe('AccountCredentialRepository (real DB)', () => {
const found = await repo.findCredentialByUsername('nonexistent');
expect(found).toBeNull();
});

// 로그인은 대소문자를 구분하지 않는다 — username 컬럼 정렬(ci)이 보장하는 계약
it.each(['ops.admin', 'OPS.ADMIN', 'Ops.Admin'])(
'대소문자가 달라도 같은 자격증명을 찾는다: %s',
async (input) => {
await createAccountCredential(prisma, { username: 'Ops.Admin' });

const found = await repo.findCredentialByUsername(input);

expect(found!.username).toBe('Ops.Admin');
},
);
});

describe('findCredentialByAccountId', () => {
Expand Down
17 changes: 17 additions & 0 deletions src/features/auth/services/auth-admin-account.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,23 @@ describe('AdminAccountService (real DB)', () => {
).rejects.toThrowDomain(400);
});

it('대문자 username은 입력 그대로 저장하고, 대소문자만 다른 username은 충돌한다', async () => {
const actor = await makeAdmin();

const created = await service.adminCreateAdmin(actor, {
...validInput,
username: 'Ops.Admin',
});
expect(created.username).toBe('Ops.Admin');

await expect(
service.adminCreateAdmin(actor, {
...validInput,
username: 'ops.admin',
}),
).rejects.toThrowDomain('USERNAME_TAKEN');
});

it('판매자가 쓰는 username도 충돌한다(자격증명 테이블 공용)', async () => {
const actor = await makeAdmin();
await createAccountCredential(prisma, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ describe('AdminCreateSellerInput', () => {
expect(await validate(build(valid))).toHaveLength(0);
});

it('username에 영문 대문자를 허용한다', async () => {
expect(
await validate(build({ ...valid, username: 'Shop.Owner' })),
).toHaveLength(0);
});

it('store 누락 거절', async () => {
const { store: _store, ...withoutStore } = valid;
const errors = await validate(build(withoutStore));
Expand Down Expand Up @@ -69,7 +75,7 @@ describe('AdminCreateSellerInput', () => {
});

it.each([
['username 대문자', { username: 'Shop' }],
['username 허용 외 문자', { username: 'shop@owner' }],
['email 형식', { email: 'nope' }],
['businessName 누락', { businessName: undefined }],
])('%s 거절', async (_label, overrides) => {
Expand Down
15 changes: 15 additions & 0 deletions src/features/store/services/store-admin-seller.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,21 @@ describe('AdminSellerService (real DB)', () => {
).rejects.toThrowDomain(400);
});

it('대소문자만 다른 username도 충돌한다', async () => {
const actor = await admin();
await createAccountCredential(prisma, {
account_type: 'SELLER',
username: 'Cake.Shop',
});

await expect(
service.adminCreateSeller(actor, {
...validInput,
username: 'cake.shop',
}),
).rejects.toThrowDomain('USERNAME_TAKEN');
});

it('감사 기록이 실패하면 계정·매장도 롤백된다(같은 트랜잭션)', async () => {
const actor = await admin();
const auditLogs = service['auditLogs'];
Expand Down
2 changes: 1 addition & 1 deletion src/features/store/store-admin-seller.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ input AdminSellerListInput {
"""
input AdminCreateSellerInput {
"""
로그인 username. 4~80자, 소문자·숫자·`.`·`_`·`-`만 허용. 이미 쓰이고 있으면 BAD_USER_INPUT.
로그인 username. 4~80자, 영문 대소문자·숫자·`.`·`_`·`-`만 허용. 이미 쓰이고 있으면(대소문자만 달라도) BAD_USER_INPUT.
"""
username: String!
"""
Expand Down
Loading