feat: add group recipe sharing and translation
This commit is contained in:
+56
-1
@@ -6,15 +6,17 @@ import { SESSION_COOKIE_NAME } from '../src/plugins/auth.js';
|
||||
import {
|
||||
createTestConfig,
|
||||
FakeAllowedEmailRepository,
|
||||
FakeGroupRepository,
|
||||
FakeRecipeRepository,
|
||||
fakeUserRepository,
|
||||
recipeDraft,
|
||||
source,
|
||||
} from './helpers/fakes.js';
|
||||
|
||||
function createDependencies(recipeRepository) {
|
||||
function createDependencies(recipeRepository, groupRepository = new FakeGroupRepository()) {
|
||||
return {
|
||||
allowedEmailRepository: new FakeAllowedEmailRepository(),
|
||||
groupRepository,
|
||||
recipeRepository,
|
||||
userRepository: fakeUserRepository,
|
||||
sourceExtractor: { extract: async () => source },
|
||||
@@ -336,3 +338,56 @@ test('import preview와 소유자별 Recipe CRUD가 이어진다', async (t) =>
|
||||
});
|
||||
assert.equal(deleteResponse.statusCode, 204);
|
||||
});
|
||||
|
||||
test('같은 그룹 사용자는 레시피를 함께 조회하지만 작성자만 수정하고 삭제한다', async (t) => {
|
||||
const recipeRepository = new FakeRecipeRepository();
|
||||
const groupRepository = new FakeGroupRepository([
|
||||
{ memberGoogleSubs: ['google-user-1', 'google-user-2'] },
|
||||
]);
|
||||
const app = await buildApp({
|
||||
config: createTestConfig(),
|
||||
dependencies: createDependencies(recipeRepository, groupRepository),
|
||||
});
|
||||
t.after(() => app.close());
|
||||
|
||||
const createResponse = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/api/recipes',
|
||||
headers: { cookie: authCookie(app, 'google-user-1') },
|
||||
payload: { recipe: recipeDraft, source },
|
||||
});
|
||||
assert.equal(createResponse.statusCode, 201);
|
||||
const created = createResponse.json().recipe;
|
||||
|
||||
const sharedListResponse = await app.inject({
|
||||
method: 'GET',
|
||||
url: '/api/recipes',
|
||||
headers: { cookie: authCookie(app, 'google-user-2') },
|
||||
});
|
||||
assert.deepEqual(
|
||||
sharedListResponse.json().recipes.map((recipe) => recipe._id),
|
||||
[created._id],
|
||||
);
|
||||
|
||||
const sharedDetailResponse = await app.inject({
|
||||
method: 'GET',
|
||||
url: `/api/recipes/${created._id}`,
|
||||
headers: { cookie: authCookie(app, 'google-user-2') },
|
||||
});
|
||||
assert.equal(sharedDetailResponse.statusCode, 200);
|
||||
|
||||
const updateResponse = await app.inject({
|
||||
method: 'PATCH',
|
||||
url: `/api/recipes/${created._id}`,
|
||||
headers: { cookie: authCookie(app, 'google-user-2') },
|
||||
payload: { title: '다른 사용자의 수정' },
|
||||
});
|
||||
assert.equal(updateResponse.statusCode, 404);
|
||||
|
||||
const deleteResponse = await app.inject({
|
||||
method: 'DELETE',
|
||||
url: `/api/recipes/${created._id}`,
|
||||
headers: { cookie: authCookie(app, 'google-user-2') },
|
||||
});
|
||||
assert.equal(deleteResponse.statusCode, 404);
|
||||
});
|
||||
|
||||
+23
-1
@@ -4,7 +4,12 @@ export class FakeRecipeRepository {
|
||||
}
|
||||
|
||||
async listByOwner(ownerGoogleSub) {
|
||||
return [...this.recipes.values()].filter((recipe) => recipe.ownerGoogleSub === ownerGoogleSub);
|
||||
return this.listByOwners([ownerGoogleSub]);
|
||||
}
|
||||
|
||||
async listByOwners(ownerGoogleSubs) {
|
||||
return [...this.recipes.values()]
|
||||
.filter((recipe) => ownerGoogleSubs.includes(recipe.ownerGoogleSub));
|
||||
}
|
||||
|
||||
async findById(ownerGoogleSub, recipeId) {
|
||||
@@ -12,6 +17,11 @@ export class FakeRecipeRepository {
|
||||
return recipe?.ownerGoogleSub === ownerGoogleSub ? recipe : null;
|
||||
}
|
||||
|
||||
async findByIdForOwners(ownerGoogleSubs, recipeId) {
|
||||
const recipe = this.recipes.get(recipeId);
|
||||
return ownerGoogleSubs.includes(recipe?.ownerGoogleSub) ? recipe : null;
|
||||
}
|
||||
|
||||
async findBySource(ownerGoogleSub, platform, sourceId) {
|
||||
return [...this.recipes.values()].find((recipe) => (
|
||||
recipe.ownerGoogleSub === ownerGoogleSub
|
||||
@@ -50,6 +60,18 @@ export class FakeRecipeRepository {
|
||||
}
|
||||
}
|
||||
|
||||
export class FakeGroupRepository {
|
||||
constructor(groups = []) {
|
||||
this.groups = groups;
|
||||
}
|
||||
|
||||
async listMemberGoogleSubs(googleSub) {
|
||||
return [...new Set(this.groups
|
||||
.filter((group) => group.memberGoogleSubs.includes(googleSub))
|
||||
.flatMap((group) => group.memberGoogleSubs))];
|
||||
}
|
||||
}
|
||||
|
||||
export class FakeAllowedEmailRepository {
|
||||
constructor(accounts = [
|
||||
{ email: 'google-user-1@example.com', canManageAccess: true },
|
||||
|
||||
@@ -23,6 +23,7 @@ test('Windows에서 개발용 MongoDB와 로컬 이미지 프로필을 자동
|
||||
assert.equal(config.mongoFallbackUri, 'mongodb://172.16.0.7:27017/our_recipe_atlas');
|
||||
assert.deepEqual(config.mongoCollections, {
|
||||
allowedEmails: 'allowed_google_emails',
|
||||
groups: 'groups_dev',
|
||||
recipes: 'recipes_dev',
|
||||
users: 'users_dev',
|
||||
});
|
||||
@@ -43,6 +44,7 @@ test('OS 프로필이 환경파일의 개발·운영 선택값보다 우선한
|
||||
assert.equal(linuxConfig.mongoUri, 'mongodb://192.168.0.240:27017/our_recipe_atlas');
|
||||
assert.deepEqual(linuxConfig.mongoCollections, {
|
||||
allowedEmails: 'allowed_google_emails',
|
||||
groups: 'groups',
|
||||
recipes: 'recipes',
|
||||
users: 'users',
|
||||
});
|
||||
|
||||
@@ -71,5 +71,7 @@ test('MiniMax 사고 과정 분리와 충분한 출력 한도를 요청한다',
|
||||
assert.equal(request.max_completion_tokens, 8192);
|
||||
assert.match(request.messages[0].content, /최대 3개/);
|
||||
assert.match(request.messages[0].content, /한식, 중식/);
|
||||
assert.match(request.messages[0].content, /외국어.*자연스러운 한국어로 번역/);
|
||||
assert.match(request.messages[0].content, /수량, 온도, 시간 값은 변환하지 않습니다/);
|
||||
assert.equal(result.ingredientGroups[0].name, '재료');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user