feat: 작업실 저장 개편 — 전체문서 PUT을 프로젝트 단위 PUT으로 + LOW 2건
저장 개편(리뷰 #4 근본치료): - PUT /api/workshop/project/:id — 서버가 디스크의 현재 문서에 해당 프로젝트만 upsert. 대시보드 저장과 채팅 도구 저장이 겹쳐도 서로 다른 프로젝트 편집이 통째로 사라지지 않음. DELETE /api/workshop/project/:id, PUT /api/workshop/active(포인터 단독) 신설. 옛 전체문서 PUT은 스테일 탭 호환으로 유지 - 클라이언트: dirtyProjects 추적 → saveProject는 바뀐 프로젝트만 순차 PUT, 활성 포인터는 putActive() 즉시 저장, 프로젝트 삭제는 DELETE 직접 호출, beforeunload flush도 프로젝트 단위 keepalive PUT으로 전환 - upsertProject/removeProject를 순수함수로 추출, 단위테스트 4건 LOW 2건: - loadFiles에 요청 일련번호 — 빠른 프로젝트 전환 시 옛 응답이 나중에 도착해 이전 프로젝트 파일을 새 판에 그리는 레이스 제거 - 슬라이서 인계 ?load=를 JSON 배열 인코딩으로 전환(파일명에 쉼표 있으면 옛 쉼표 join이 깨지던 문제). 슬라이서 쪽은 JSON 우선, 옛 형식 폴백 유지 Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -36,4 +36,43 @@ test('projectToReadme: 개요 섹션은 description이 있을 때만', () => {
|
||||
test('projectToReadme: 부품 링크를 들여쓰기로 실음', () => {
|
||||
const md = projectToReadme(sampleProject());
|
||||
assert.ok(md.includes(' - 구매처: https://example.com'));
|
||||
});
|
||||
});
|
||||
// 프로젝트 단위 저장 API(2026-09-24)의 upsert/remove — 서버는 디스크의 현재 문서에
|
||||
// 해당 프로젝트만 반영하므로 다른 프로젝트는 절대 덮이지 않아야 한다.
|
||||
import { upsertProject, removeProject, WorkshopData } from '../src/gateway/routes/workshop-storage.js';
|
||||
|
||||
function sampleData(): WorkshopData {
|
||||
const a = sampleProject({ id: 'proj_a', name: 'A' });
|
||||
const b = sampleProject({ id: 'proj_b', name: 'B' });
|
||||
return { projects: [a, b], activeProjectId: 'proj_a' };
|
||||
}
|
||||
|
||||
test('upsertProject: 기존 프로젝트는 교체, 나머지는 보존', () => {
|
||||
const data = sampleData();
|
||||
const edited = sampleProject({ id: 'proj_b', name: 'B-수정됨', notes: '채팅 도구가 저장' });
|
||||
upsertProject(data, edited);
|
||||
assert.equal(data.projects.length, 2);
|
||||
assert.equal(data.projects[1].name, 'B-수정됨');
|
||||
// 다른 프로젝트(A)의 내용은 그대로 — 전체문서 PUT이던 시절엔 여기가 유실됐다
|
||||
assert.equal(data.projects[0].name, 'A');
|
||||
assert.equal(data.projects[0].parts[0].name, 'STS3215 서보');
|
||||
});
|
||||
|
||||
test('upsertProject: 없는 프로젝트는 추가, 활성 포인터가 무효하면 복구', () => {
|
||||
const data = sampleData();
|
||||
data.activeProjectId = 'proj_gone';
|
||||
upsertProject(data, sampleProject({ id: 'proj_new', name: '새것' }));
|
||||
assert.equal(data.projects.length, 3);
|
||||
assert.equal(data.activeProjectId, 'proj_a');
|
||||
});
|
||||
|
||||
test('removeProject: 삭제되면 true, 활성 포인터가 가리키던 프로젝트면 첫 프로젝트로', () => {
|
||||
const data = sampleData();
|
||||
assert.equal(removeProject(data, 'proj_a'), true);
|
||||
assert.equal(data.projects.length, 1);
|
||||
assert.equal(data.activeProjectId, 'proj_b');
|
||||
});
|
||||
|
||||
test('removeProject: 없는 프로젝트면 false(404 응답용)', () => {
|
||||
assert.equal(removeProject(sampleData(), 'proj_none'), false);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user