Files
WebDrop/web/src/core-bridge/worker/accountVault.test.ts
T
justme 4b8d0b5a5f
ci/woodpecker/push/web-build Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/dev-image Pipeline was successful
feat(web): discord-style chat, members pane, invitations and optimistic send [CI]
- chat: left-aligned discord-style messages with grouped senders, hover
  actions, an auto-growing composer and file cards
- send: render messages and files locally first and replace the
  placeholder when the worker publishes the real item; failures keep the
  text with a retry action
- navigation: space rail plus a flatter channel list with the space name
  header and collapsible groups
- members: right pane tabs (files / members) grouped by online, offline
  and pending users, with a prominent invite action and a top bar shortcut
- notifications: toast host for invite results; invitees get a toast with
  an accept action, and the invitation payload now carries the space name
  and inviter inside the encrypted grant
- users over devices: drop device names, the device list and device
  rename/release; senders show the account (guests show as guest) and the
  share panel only keeps links
- follow contract 7.0.0 (max_members, members_used, members_full, /join)
- docs: ui-design and frontend updated for the new layout and concepts
2026-10-14 22:19:25 +08:00

29 lines
1.9 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { exportResource, mergeVault, type VaultProfile } from './accountVault';
import type { SavedResource } from '../database';
const resource = (modifiedAt = 10): SavedResource => ({ id: 'AAAAAAAAAAAAAAAAAAAAAA', kind: 'channel', mode: 'enc', role: 'owner',
link: 'https://drop.test/c/a#k=secret', endpointSecret: new Uint8Array(32).fill(7), localScope: 'account:alice',
createdAt: 1, modifiedAt, expiresAt: 100, maxMembers: 3, name: `version-${modifiedAt}` });
const profile = (): VaultProfile => ({ v: 3, keys: { publicKey: {}, privateKey: {}, signing: { publicKey: {}, privateKey: {} } }, resources: [exportResource(resource(20))], deletes: [] });
describe('account vault portability and merge', () => {
it('never synchronizes device identity or local scope', () => {
const exported = exportResource(resource());
expect(exported).not.toHaveProperty('endpointSecret');
expect(exported).not.toHaveProperty('localScope'); expect(exported.link).toContain('secret');
});
it('a stale device does not overwrite a newer remote resource', () => {
expect(mergeVault(profile(), [resource(10)], []).resources[0]?.name).toBe('version-20');
});
it('resource removal survives a stale device syncing its old copy', () => {
const remote = { ...profile(), resources: [], deletes: [{ id: resource().id, modifiedAt: 30 }] };
expect(mergeVault(remote, [resource(20)], []).resources).toEqual([]);
expect(mergeVault(profile(), [], [{ id: resource().id, modifiedAt: 30 }]).resources).toEqual([]);
});
it('explicitly rejoining after removal restores the resource', () => {
const remote = { ...profile(), resources: [], deletes: [{ id: resource().id, modifiedAt: 30 }] };
const merged = mergeVault(remote, [resource(40)], []);
expect(merged.resources).toHaveLength(1); expect(merged.deletes).toEqual([]);
});
});