Files
homeclaw/tests/workshop-overview.test.ts
kimandClaude Code eb516e2b74 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>
2026-09-24 07:38:53 +09:00

79 lines
3.7 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// workshop-storage.ts 프로젝트 개요 문서(projectToReadme) 단위 테스트 (2026-09-23)
// 대시보드 데이터 → 마크다운 'get' 출력. readme 파일 생성은 철회됨(개요 탭으로 이동).
import test from 'node:test';
import assert from 'node:assert/strict';
import { projectToReadme, WorkshopProject } from '../src/gateway/routes/workshop-storage.js';
function sampleProject(over: Partial<WorkshopProject> = {}): WorkshopProject {
return {
id: 'proj_test',
name: 'SO-101 로봇팔',
parts: [
{ id: 'p1', name: 'STS3215 서보', qty: 12, unitPrice: 20000, status: '검토중', memo: 'Leader 6 + Follower 6', links: [{ id: 'lk1', label: '구매처', url: 'https://example.com' }] },
{ id: 'p2', name: '나사 세트', qty: 1, unitPrice: 0, status: '보유', memo: '', links: [] },
],
phases: [
{ id: 'ph1', name: '준비', tasks: [{ id: 't1', text: 'STL 다운로드', done: true }, { id: 't2', text: '출력', done: false }] },
],
notes: 'K2로 출력한다',
...over,
};
}
test('projectToReadme: 개요 섹션은 description이 있을 때만', () => {
const withDesc = projectToReadme(sampleProject({ description: 'LeRobot SO-101 오픈소스 로봇팔 조립 프로젝트' }));
assert.ok(withDesc.includes('# SO-101 로봇팔'));
assert.ok(withDesc.includes('## 개요\nLeRobot SO-101 오픈소스 로봇팔 조립 프로젝트'));
const withoutDesc = projectToReadme(sampleProject());
assert.ok(!withoutDesc.includes('## 개요'));
// 부품/작업/메모는 항상 실린다
assert.ok(withoutDesc.includes('- [검토중] STS3215 서보 × 12 = 240,000원'));
assert.ok(withoutDesc.includes('- [x] STL 다운로드'));
assert.ok(withoutDesc.includes('## 메모'));
});
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);
});