title | authors | tags | description | toc_max_heading_level | ||||||
---|---|---|---|---|---|---|---|---|---|---|
에러로그: oak testing에서 get과 set은 서로 관련이 없습니다. |
|
|
Deno oak에 테스트 코드를 작성하면서 cookie 설정이 특이하다는 점을 발견했습니다. |
6 |
Deno oak에 테스트 코드를 작성하면서 cookie 설정이 특이하다는 점을 발견했습니다.
deno oak의 testing에서 set과 get은 서로 무관합니다. createMockContext
으로 만든 context
(이하 ctx
)는 ctx.cookies.set
으로 넣었다고 ctx.cookies.get
으로 꺼낼 수 있는 것은 아닙니다. 모두
따라서 일반적인 흐름은 다음과 같습니다:
- 클라이언트가 요청을 보냄.
- 요청이 도달하면 미들웨어가 실행되어 전처리 작업 수행.
- 미들웨어는 요청을 검사하고 조작한 후, 컨트롤러에게 제어를 전달.
- 컨트롤러는 요청을 처리하고 필요한 작업을 수행.
- 컨트롤러는 응답을 생성하여 클라이언트에게 반환.
- 응답이 도달하면 미들웨어가 실행되어 후처리 작업 수행 및 응답을 클라이언트에게 전달.
ChatGPT에게 질문하고 얻은 답변입니다.
토큰 갱신이 필요하다는 응답을 받으면 프론트엔드는 어떻게 처리하는지 궁금해졌습니다.
axios interceptors와 refresh token을 활용한 jwt 토큰 관리 - HyunGyu-Kim
잘 다룬 블로그를 발견했습니다.
Deno oak를 테스트하는 방법을 찾고 있었습니다.
cookie를 서버에서 설정하고 클라이언트가 요청 보낼 때마다 확인해야 하는데 이것을 어떻게 구현하는지 찾아보고 있었습니다.
fix: mocked contexts provide the cookies property #422 - oakserver / oak
Deno.test('should set cookie from mock context', async () => {
const hash = 'hash1234';
const ctx = createMockContext();
await ctx.cookies.set('sessionID', hash, { httpOnly: true });
assertEquals(
[...ctx.response.headers],
[['set-cookie', `sessionID=${hash}; path=/; httponly`]]
);
});
Deno.test('should get cookie from mock context', async () => {
const hash = 'hash1234';
const ctx = createMockContext({
headers: [['cookie', `sessionID=${hash};`]],
});
assertEquals(await ctx.cookies.get('sessionID'), hash);
});
테스트 코드가 더럽지만 일단 어떻게 작성해야 하는지 알 수 있습니다.
보통 토큰은 2개로 테스트합니다. 하지만 이것에 대한 테스트 코드가 별로 없는 것도 의외입니다.
import type { Context, Middleware } from '../deps.ts';
import Token from '../util/token.ts';
const token = Token.getInstance();
const authMiddleware: Middleware = async (
{ request, response, cookies },
next
) => {
const accessToken = request.headers.get('Authorization');
if (accessToken) {
await next();
} else {
const refreshToken = await cookies.get('user');
if (!refreshToken) {
response.status = 400;
response.body = {
msg: '로그아웃 되었습니다.',
};
} else {
response.status = 401;
response.body = {
msg: '토큰이 만료되었습니다.',
accessToken: token.refreshAccessToken(refreshToken),
};
}
}
};
export { authMiddleware };
이것에 대한 테스트코드를 작성하고 있는데 문제가 생겼습니다.
Deno.test('access token이 만료 refresh token은 유효',
async () => {
const ctx = testing.createMockContext({
headers: [['Authorization', '']],
});
const refreshCookie = 'refreshToken';
const next = testing.createMockNext();
await ctx.cookies.set('user', refreshCookie);
await authMiddleware(ctx, next);
assertEquals(
[...ctx.response.headers],
[['set-cookie', `user=${refreshCookie}; path=/; httponly`]]
);
await authMiddleware(ctx, next);
console.log(await ctx.cookies.get('user'), ctx.response.headers); // undefined
assert(ctx.response.status === 401, '401');
const configPromise = new
}
);
cookies
를 set
해도 get
할 수 없습니다.
Deno.test('access token이 만료 refresh token은 유효', async () => {
const ctx = testing.createMockContext({
headers: [['Authorization', '']],
});
const refreshCookie = 'refreshToken';
const next = testing.createMockNext();
await authMiddleware(ctx, next); // authMiddleware를 호출하기 전에 쿠키를 설정하지 않습니다.
// 쿠키 설정 후에 await를 사용하여 Promise가 완료될 때까지 기다립니다.
await ctx.cookies.set('user', refreshCookie);
await authMiddleware(ctx, next);
assertEquals(
[...ctx.response.headers],
[['set-cookie', `user=${refreshCookie}; path=/; httponly`]]
);
assert(ctx.response.status === 401, '401');
});
표본이 너무 작아서 GPT로 해결할 수 없었습니다.
Deno.test('access token이 만료 refresh token은 유효', async () => {
const refreshCookie = 'refreshToken';
const ctx = testing.createMockContext({
headers: [['Authorization', '']],
});
const next = testing.createMockNext();
await ctx.cookies.set('user', refreshCookie).then(() => {
authMiddleware(ctx, next);
});
assertEquals(
[...ctx.response.headers],
[['set-cookie', `user=${refreshCookie}; path=/; httponly`]]
);
assert(ctx.response.status === 401, '401');
assertEquals(
ctx.response.body,
{
msg: '토큰이 만료되었습니다.',
accessToken: async () => ({ accessToken: null, success: false }),
},
'access token 갱신 응답'
);
});
순서에 맞게 실행했지만 그래도 테스트를 통과하지 않았습니다.
이 테스트 코드를 보면 확실하게 동작합니다. 하지만 set의 동작방식이 다릅니다.
Deno.test('should get cookie from mock context', async () => {
const hash = 'hash1234';
const ctx = createMockContext({
headers: [['cookie', `sessionID=${hash};`]],
});
assertEquals(await ctx.cookies.get('sessionID'), hash);
});
github에서 이 예시를 보니까 cookies
를 set
하는 동작이 다릅니다. 즉 set
을 하면 get
으로 접근이 가능한 것이 아니었습니다.
fix: mocked contexts provide the cookies property #422 - oakserver / oak
Deno.test('access token이 만료 refresh token은 유효',
async () => {
const refreshCookie = 'refreshToken';
const ctx = testing.createMockContext({
headers: [
['Authorization', ''],
['cookie', `user=${refreshCookie}`],
],
});
const next = testing.createMockNext();
await authMiddleware(ctx, next);
assert(ctx.response.status === 401, '401');
assertEquals(
ctx.response.body,
{
msg: '토큰이 만료되었습니다.',
accessToken: Promise.resolve({ accessToken: null, success: false }),
},
'access token 갱신 응답'
);
},
});
결국 이렇게 해서 문제를 해결했습니다. 상당히 특이하고 시간을 많이 낭비하면서 겨우 힘을게 테스트를 구현했습니다.
set
을 하면 당연히 get
메서드로 접근 가능할 것이라는 순진한 생각을 가졌습니다. 저는 자바스크립트 생태계에 대한 높은 신뢰를 갖는 실수를 오늘도 하고 말았습니다.
부조리 맞습니다. 나중에 이거 수정하는 PR올려 컨트리뷰터가 되고 싶네요.