Replaces LICENSE (GPLv3 -> AGPLv3) and prepends `SPDX-License-Identifier: AGPL-3.0-or-later` to every source file across decnet/, decnet_web/, tests/, scripts/, and tools/. Rationale: closes the GPLv3 ASP loophole so any party operating a modified DECNET as a network service must offer their modified source. Personal copyright (Samuel Paschuan) + inbound=outbound contributions make a future unilateral relicense infeasible. - LICENSE: full AGPL-3.0 text (gnu.org/licenses/agpl-3.0.txt) - COPYRIGHT: project copyright notice - tools/add_spdx_headers.py: idempotent header injector (shebang- and PEP 263-aware) Touches 1565 source files (.py, .ts, .tsx, .js, .jsx, .css, .sh). No behavior change; comments only.
102 lines
2.8 KiB
TypeScript
102 lines
2.8 KiB
TypeScript
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import { render, screen } from '@testing-library/react';
|
|
import userEvent from '@testing-library/user-event';
|
|
import {
|
|
FileDropModal, loadFileDrops, saveFileDrops,
|
|
FILEDROP_LS_KEY, type FileDropEntry,
|
|
} from './FileDropModal';
|
|
import type { DeckyOption, TopologyOption } from './types';
|
|
|
|
vi.mock('../../hooks/useFocusTrap', () => ({ useFocusTrap: () => {} }));
|
|
|
|
const deckies: DeckyOption[] = [{ name: 'decoy-01' }];
|
|
const topologies: TopologyOption[] = [{ id: 't-1', name: 'corp', status: 'active' }];
|
|
|
|
beforeEach(() => {
|
|
localStorage.clear();
|
|
});
|
|
|
|
const sampleEntry = (): FileDropEntry => ({
|
|
id: 'fd-1',
|
|
decky_name: 'd',
|
|
topology_id: null,
|
|
path: '/tmp/x',
|
|
size_bytes: 1,
|
|
filename: 'x',
|
|
mode: 0o644,
|
|
mtime_offset: 0,
|
|
dropped_at: '2026-05-09T11:00:00Z',
|
|
});
|
|
|
|
describe('loadFileDrops / saveFileDrops', () => {
|
|
it('returns [] when localStorage is empty', () => {
|
|
expect(loadFileDrops()).toEqual([]);
|
|
});
|
|
|
|
it('round-trips through localStorage', () => {
|
|
saveFileDrops([sampleEntry()]);
|
|
const out = loadFileDrops();
|
|
expect(out).toHaveLength(1);
|
|
expect(out[0].id).toBe('fd-1');
|
|
});
|
|
|
|
it('caps to 200 entries on save', () => {
|
|
const many: FileDropEntry[] = Array.from({ length: 250 }, (_, i) => ({
|
|
...sampleEntry(), id: `fd-${i}`,
|
|
}));
|
|
saveFileDrops(many);
|
|
const stored = JSON.parse(localStorage.getItem(FILEDROP_LS_KEY) ?? '[]');
|
|
expect(stored).toHaveLength(200);
|
|
});
|
|
|
|
it('returns [] on malformed JSON in storage', () => {
|
|
localStorage.setItem(FILEDROP_LS_KEY, '{not-an-array');
|
|
expect(loadFileDrops()).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe('FileDropModal', () => {
|
|
it('renders the title and the Fleet/MazeNET toggle', () => {
|
|
render(
|
|
<FileDropModal
|
|
deckies={deckies}
|
|
topologies={topologies}
|
|
onClose={() => {}}
|
|
onDropped={() => {}}
|
|
/>,
|
|
);
|
|
expect(screen.getByText('DROP FILE ON DECKY')).toBeInTheDocument();
|
|
expect(screen.getByText('Fleet')).toBeInTheDocument();
|
|
});
|
|
|
|
it('renders the bypass-warning banner', () => {
|
|
render(
|
|
<FileDropModal
|
|
deckies={deckies}
|
|
topologies={topologies}
|
|
onClose={() => {}}
|
|
onDropped={() => {}}
|
|
/>,
|
|
);
|
|
expect(
|
|
screen.getByText(/File drops bypass canary instrumentation/),
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
it('CANCEL invokes onClose', async () => {
|
|
const onClose = vi.fn();
|
|
const user = userEvent.setup();
|
|
render(
|
|
<FileDropModal
|
|
deckies={deckies}
|
|
topologies={topologies}
|
|
onClose={onClose}
|
|
onDropped={() => {}}
|
|
/>,
|
|
);
|
|
await user.click(screen.getByText('CANCEL'));
|
|
expect(onClose).toHaveBeenCalled();
|
|
});
|
|
});
|