Skip to content
Closed
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
3 changes: 3 additions & 0 deletions .jules/palette.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,6 @@
## 2026-07-30 - Add window.confirm for destructive actions
**Learning:** Destructive actions like deleting groups and edge relationships previously occurred immediately without user confirmation.
**Action:** Always wrap delete operations with window.confirm() dialogs and ensure corresponding tests successfully mock window.confirm.
## 2026-08-14 - Confirm destructive actions like table deletion
**Learning:** Destructive actions such as deleting a table require explicit user confirmation to prevent accidental data loss.
**Action:** Always wrap delete operations with window.confirm() dialogs and ensure corresponding tests successfully mock window.confirm.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
# Changelog

## Unreleased

- [FE] 🛡️ **테이블 삭제 확인**: 편집 모달에서 테이블 이름을 포함한 확인 대화상자를 표시하고, 사용자가 명시적으로 확인한 경우에만 기존 삭제 경로를 실행합니다. 취소하면 테이블을 그대로 보존합니다.

- [BE] 🔒 **Cryptography 50+ 보안 경계 갱신**: `pyproject.toml`과 두 hash-locked 요구사항 파일을 동일한 Cryptography 50+ 해석으로 정합화하여 PKCS#7 오류·타이밍 구분으로 인한 CVE-2026-69247 완화를 실제 설치·검증 경로에 반영했습니다.

- [FE] ⚡ **검색 노드 참조 안정화 및 순차 스냅샷 폴링**: 같은 정규화 검색어와 원본 테이블 데이터에는 장식된 `node.data` 참조를 재사용하여 드래그 중 불필요한 하위 렌더링과 할당을 줄입니다. 스냅샷 폴링은 이전 요청이 끝난 뒤에만 다음 요청을 예약하며, 선택 변경·언마운트 후 도착한 오래된 성공 또는 실패 응답을 무시합니다.
- [BE] 🔒 **공유 export 전 경로 redaction**: 공개 share의 SQL / index-design / reversing-spec export에서 코멘트·`example_value`를 제거합니다. 단위 테스트로 누출을 차단합니다.
- [BE] 🛠️ **함수 인덱스 중복 오탐 수정**: `lower(email)` 등 expression index를 평문 컬럼 인덱스의 중복으로 잘못 판단하지 않도록 괄호 파서를 강화했습니다.
Expand Down
48 changes: 48 additions & 0 deletions frontend/src/components/modals/EditTableModal.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,54 @@ describe('EditTableModal', () => {
expect(screen.getByRole('checkbox', { name: 'test_col NN 설정' })).toBeInTheDocument();
});

it('deletes a table when 테이블 삭제 is clicked and confirmed', async () => {
const onDeleteTableMock = vi.fn();
const editingNode = {
id: 'table-1',
type: 'table',
position: { x: 0, y: 0 },
data: {
title: 'test_table',
comment: '',
columns: []
}
};

vi.spyOn(window, 'confirm').mockReturnValue(true);

render(<EditTableModal {...defaultProps} editingNode={editingNode as any} onDeleteTable={onDeleteTableMock} />);

const user = userEvent.setup();
await user.click(screen.getByRole('button', { name: '테이블 삭제' }));

expect(window.confirm).toHaveBeenCalledWith("'test_table' 테이블을 삭제하시겠습니까?");
expect(onDeleteTableMock).toHaveBeenCalled();
});

it('does not delete a table when 테이블 삭제 is clicked and canceled', async () => {
const onDeleteTableMock = vi.fn();
const editingNode = {
id: 'table-1',
type: 'table',
position: { x: 0, y: 0 },
data: {
title: 'test_table',
comment: '',
columns: []
}
};

vi.spyOn(window, 'confirm').mockReturnValue(false);

render(<EditTableModal {...defaultProps} editingNode={editingNode as any} onDeleteTable={onDeleteTableMock} />);

const user = userEvent.setup();
await user.click(screen.getByRole('button', { name: '테이블 삭제' }));

expect(window.confirm).toHaveBeenCalled();
expect(onDeleteTableMock).not.toHaveBeenCalled();
});

it('returns null if not open or no editingNode', () => {
const { container } = render(<EditTableModal {...defaultProps} isOpen={false} editingNode={null} />);
expect(container.firstChild).toBeNull();
Expand Down
5 changes: 4 additions & 1 deletion frontend/src/components/modals/EditTableModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,10 @@ export function EditTableModal({
<div className="row" style={{ gap: 8 }}>
<button
type="button"
onClick={onDeleteTable}
onClick={() => {
if (!window.confirm(`'${editingNode.data.title}' 테이블을 삭제하시겠습니까?`)) return;
onDeleteTable();
}}
style={{ color: "#b91c1c", borderColor: "#fca5a5" }}
>
테이블 삭제
Expand Down
1 change: 1 addition & 0 deletions frontend/src/components/modals/ModalCoverage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ describe('modal behavior coverage', () => {
expect(deleteEditing(tableNode)?.data.columns).toHaveLength(1)

fireEvent.submit(document.getElementById('editTableForm')!)
vi.spyOn(window, 'confirm').mockReturnValueOnce(true)
fireEvent.click(screen.getByRole('button', { name: '테이블 삭제' }))
fireEvent.click(screen.getByRole('button', { name: '복제' }))
const duplicate = setNodes.mock.calls[2]?.[0] as (nodes: Node<TableNodeData>[]) => Node<TableNodeData>[]
Expand Down
Loading