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
24 changes: 7 additions & 17 deletions src/attendance/ecampus.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,38 +4,28 @@ import {
InternalServerErrorException,
UnauthorizedException,
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { PasswordPayload } from 'src/auth/payload/password.payload';
import { UserInfo } from 'src/auth/types/user-info.type';
import { AxiosService } from 'src/common/services/axios.service';
import { ExternalApiService } from 'src/common/services/external-api.service';
import { AttendanceRepository } from './attendance.repository';
import { RawCourse } from './types/raw-course';

@Injectable()
export class EcampusService {
constructor(
private readonly attendanceRepository: AttendanceRepository,
private readonly axiosService: AxiosService,
private readonly configService: ConfigService,
private readonly externalApiService: ExternalApiService,
) {}

async updateUserAttendance(
user: UserInfo,
payload: PasswordPayload,
): Promise<void> {
const response = await this.axiosService.post(
this.configService.get<string>('GET_COURSE_ATTENDANCE_URL'),
JSON.stringify({
studentId: user.studentId,
name: user.name,
password: payload.password,
}),
{
headers: {
'Content-Type': 'application/json',
},
},
);
const response = await this.externalApiService.fetchCourseAttendance({
studentId: user.studentId,
name: user.name,
password: payload.password,
});

if (response.status === 500) {
throw new InternalServerErrorException('출석 업데이트 람다 함수 오류');
Expand Down
22 changes: 6 additions & 16 deletions src/auth/auth.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { AxiosService } from 'src/common/services/axios.service';
import { ExternalApiService } from 'src/common/services/external-api.service';
import { AuthRepository } from './auth.repository';
import { TokenDto } from './dto/token.dto';
import { SignUpPayload } from './payload/signup.payload';
Expand All @@ -11,25 +10,16 @@ import { PortalUserInfo } from './types/portal-user.type';
export class AuthService {
constructor(
private readonly tokenService: TokenService,
private readonly configService: ConfigService,
private readonly axiosService: AxiosService,
private readonly externalApiService: ExternalApiService,
private readonly authRepository: AuthRepository,
// private readonly ecampusService: EcampusService,
) {}

async signup(payload: SignUpPayload): Promise<TokenDto> {
const res = await this.axiosService.post(
this.configService.get('PORTAL_AUTH_URL'),
JSON.stringify({
id: payload.studentId,
password: payload.password,
}),
{
headers: {
'Content-Type': 'application/json',
},
},
);
const res = await this.externalApiService.authenticatePortal({
studentId: payload.studentId,
password: payload.password,
});

if (res.status !== 200) {
throw new UnauthorizedException(
Expand Down
63 changes: 63 additions & 0 deletions src/common/common.module.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { Injectable } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { Test } from '@nestjs/testing';
import { CommonModule } from './common.module';
import { AxiosService } from './services/axios.service';
import { DiscordService } from './services/discord.service';
import { ExternalApiService } from './services/external-api.service';
import { FcmService } from './services/fcm.service';
import { PasswordService } from './services/password.service';

@Injectable()
class ExternalApiConsumer {
constructor(private readonly externalApiService: ExternalApiService) {}

authenticate() {
return this.externalApiService.authenticatePortal({
studentId: '20240001',
password: 'secret',
});
}
}

describe('CommonModule', () => {
it('다른 모듈의 서비스에 ExternalApiService를 주입한다', async () => {
// Given
const response = { data: '{}', status: 200 };
const post = jest.fn().mockResolvedValue(response);
const moduleRef = await Test.createTestingModule({
imports: [
ConfigModule.forRoot({
isGlobal: true,
ignoreEnvFile: true,
load: [
() => ({ PORTAL_AUTH_URL: 'https://external.test/portal-auth' }),
],
}),
CommonModule,
],
providers: [ExternalApiConsumer],
})
.overrideProvider(AxiosService)
.useValue({ get: jest.fn(), post })
.overrideProvider(DiscordService)
.useValue({})
.overrideProvider(FcmService)
.useValue({})
.overrideProvider(PasswordService)
.useValue({})
.compile();
const consumer = moduleRef.get(ExternalApiConsumer);

// When
const result = await consumer.authenticate();

// Then
expect(result).toBe(response);
expect(post).toHaveBeenCalledWith(
'https://external.test/portal-auth',
JSON.stringify({ id: '20240001', password: 'secret' }),
{ headers: { 'Content-Type': 'application/json' } },
);
});
});
4 changes: 3 additions & 1 deletion src/common/common.module.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Global, Module } from '@nestjs/common';
import { AxiosService } from './services/axios.service';
import { DiscordService } from './services/discord.service';
import { ExternalApiService } from './services/external-api.service';
import { FcmService } from './services/fcm.service';
import { PasswordService } from './services/password.service';
import { PrismaService } from './services/prisma.service';
Expand All @@ -10,13 +11,14 @@ import { PrismaService } from './services/prisma.service';
providers: [
PrismaService,
AxiosService,
ExternalApiService,
PasswordService,
FcmService,
DiscordService,
],
exports: [
PrismaService,
AxiosService,
ExternalApiService,
PasswordService,
FcmService,
DiscordService,
Expand Down
140 changes: 140 additions & 0 deletions src/common/services/external-api-godok.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { ConfigService } from '@nestjs/config';
import { Test } from '@nestjs/testing';
import { AxiosService } from './axios.service';
import { ExternalApiService } from './external-api.service';

describe('ExternalApiService godok API', () => {
const response = { data: '{}', status: 200 };
const endpointByKey: Record<string, string> = {
GET_GODOK_CALENDAR_URL: 'https://external.test/godok/calendar',
CREATE_GODOK_RESERVATION_URL: 'https://external.test/godok/create',
GET_USER_GODOK_RESERVATIONS_URL: 'https://external.test/godok/reservations',
CANCEL_GODOK_RESERVATION_URL: 'https://external.test/godok/cancel',
GET_USER_GODOK_STATUS_URL: 'https://external.test/godok/status',
};

let service: ExternalApiService;
let get: jest.Mock;
let post: jest.Mock;

beforeEach(async () => {
get = jest.fn().mockResolvedValue(response);
post = jest.fn().mockResolvedValue(response);

const moduleRef = await Test.createTestingModule({
providers: [
ExternalApiService,
{ provide: AxiosService, useValue: { get, post } },
{
provide: ConfigService,
useValue: {
getOrThrow: jest.fn((key: string) => endpointByKey[key]),
},
},
],
}).compile();

service = moduleRef.get(ExternalApiService);
});

it('고전독서 캘린더를 GET으로 조회한다', async () => {
// Given
get.mockClear();

// When
const result = await service.fetchGodokCalendar();

// Then
expect(get).toHaveBeenCalledWith(endpointByKey.GET_GODOK_CALENDAR_URL);
expect(result).toBe(response);
});

it.each([
{
name: '예약 생성',
endpoint: endpointByKey.CREATE_GODOK_RESERVATION_URL,
body: {
student_id: '20240001',
password: 'secret',
shInfoId: 'slot-1',
bkCode: 11,
bkAreaCode: 1000,
},
invoke: () =>
service.createGodokReservation({
userId: '20240001',
password: 'secret',
godokSlotId: 'slot-1',
bookCode: 11,
bookAreaCode: 1000,
}),
},
{
name: '예약 목록',
endpoint: endpointByKey.GET_USER_GODOK_RESERVATIONS_URL,
body: { student_id: '20240001', password: 'secret' },
invoke: () =>
service.fetchGodokReservations({
userId: '20240001',
password: 'secret',
}),
},
{
name: '예약 취소',
endpoint: endpointByKey.CANCEL_GODOK_RESERVATION_URL,
body: {
student_id: '20240001',
password: 'secret',
opAppInfoId: 'reservation-1',
},
invoke: () =>
service.cancelGodokReservation({
userId: '20240001',
password: 'secret',
reservationId: 'reservation-1',
}),
},
{
name: '사용자 상태',
endpoint: endpointByKey.GET_USER_GODOK_STATUS_URL,
body: { student_id: '20240001', password: 'secret' },
invoke: () =>
service.fetchGodokStatus({
userId: '20240001',
password: 'secret',
}),
},
])(
'$name 요청을 외부 규격으로 전송한다',
async ({ endpoint, body, invoke }) => {
// Given
post.mockClear();

// When
const result = await invoke();

// Then
expect(post).toHaveBeenCalledWith(endpoint, JSON.stringify(body), {
headers: { 'Content-Type': 'application/json' },
});
expect(result).toBe(response);
},
);

it('고전독서 도서 목록을 FormData로 조회한다', async () => {
// Given
post.mockImplementation(async (url: string, formData: FormData) => {
expect(url).toBe('http://classic.sejong.ac.kr/seletTermBookList.json');
expect(formData.get('opTermId')).toBe('TERM-00571');
expect(formData.get('bkAreaCode')).toBe('1000');
return response;
});

// When
const result = await service.fetchGodokBooks(1000);

// Then
expect(post).toHaveBeenCalledTimes(1);
expect(result).toBe(response);
});
});
Loading
Loading