Files
NetMesh/infrastructure/services/adapters/webdavEncryptedObjectPath.test.ts

152 lines
5.1 KiB
TypeScript
Raw Permalink Normal View History

import assert from 'node:assert/strict';
import { describe, it } from 'node:test';
import type { SyncedFile } from '../../../domain/sync';
import { createAdapter } from './index';
import {
cloudAdapterAsEncryptedObjectStorage,
webdavEncryptedObjectCapabilities,
} from './encryptedObjectStorageBridge';
import { DEFAULT_ENCRYPTED_SYNC_OBJECT_KEY } from '../../../domain/encryptedObjectStorage';
import type { CloudAdapter } from './index';
/**
* Prove the production WebDAV factory returns a CloudAdapter that is wired
* through EncryptedObjectStorage (same surface plugins use), by exercising
* the shared bridge with a real in-memory CloudAdapter that mirrors WebDAV's
* single-file semantics and comparing with createAdapter's webdav wrap shape.
*/
function memoryWebdavAdapter(initial: SyncedFile | null = null): CloudAdapter & { store: SyncedFile | null } {
const adapter = {
store: initial,
isAuthenticated: true,
accountInfo: { id: 'webdav.example', name: 'user@webdav.example' },
resourceId: null as string | null,
signOut() {
adapter.isAuthenticated = false;
adapter.accountInfo = null;
adapter.store = null;
},
async initializeSync() {
adapter.resourceId = DEFAULT_ENCRYPTED_SYNC_OBJECT_KEY;
return adapter.resourceId;
},
async upload(file: SyncedFile) {
adapter.store = file;
adapter.resourceId = DEFAULT_ENCRYPTED_SYNC_OBJECT_KEY;
return adapter.resourceId;
},
async download() {
return adapter.store;
},
async deleteSync() {
adapter.store = null;
},
getTokens() {
return null;
},
};
return adapter;
}
describe('WebDAV EncryptedObjectStorage production path', () => {
it('createAdapter(webdav) wraps through EncryptedObjectStorage (not a raw WebDAVAdapter)', async () => {
const { default: WebDAVAdapter } = await import('./WebDAVAdapter');
const adapter = await createAdapter('webdav', undefined, undefined, {
endpoint: 'https://webdav.example.test',
authType: 'basic',
username: 'user',
password: 'secret',
});
assert.equal(adapter instanceof WebDAVAdapter, false, 'must not return raw WebDAVAdapter');
assert.equal(typeof adapter.upload, 'function');
assert.equal(typeof adapter.download, 'function');
assert.equal(typeof adapter.initializeSync, 'function');
assert.equal(typeof adapter.deleteSync, 'function');
assert.equal(adapter.getTokens(), null);
});
it('createAdapter(webdav) is authenticated when config exists so getConnectedAdapter can reuse', async () => {
const adapter = await createAdapter('webdav', undefined, undefined, {
endpoint: 'https://webdav.example.test',
authType: 'basic',
username: 'user',
password: 'secret',
});
// Pre-wrap WebDAVAdapter reported isAuthenticated whenever config existed.
// The bridge must match so manager cache reuse (existing?.isAuthenticated) works.
assert.equal(adapter.isAuthenticated, true);
});
it('createAdapter(webdav) preserves constructor resourceId and refreshes from backing adapter after initializeSync', async () => {
const persistedPath = '/netcatty-vault.json';
const adapter = await createAdapter(
'webdav',
undefined,
persistedPath,
{
endpoint: 'https://webdav.example.test',
authType: 'basic',
username: 'user',
password: 'secret',
},
);
assert.equal(
adapter.resourceId,
persistedPath,
'must not discard resourceId passed into createAdapter',
);
// Without network, initializeSync fails; still prove resourceId is not
// overwritten to the bare DEFAULT key before connect runs.
assert.notEqual(adapter.resourceId, 'netcatty-vault.json');
});
it('WebDAV-style adapters round-trip encrypted SyncedFile bytes through EncryptedObjectStorage', async () => {
const raw = memoryWebdavAdapter({
meta: {
version: 2,
updatedAt: 1,
deviceId: 'd',
appVersion: '0.0.0',
iv: 'iv',
salt: 'salt',
algorithm: 'AES-256-GCM',
kdf: 'PBKDF2',
},
payload: 'remote-cipher',
});
const storage = cloudAdapterAsEncryptedObjectStorage(raw, 'webdav', {
capabilities: webdavEncryptedObjectCapabilities(),
});
const caps = await storage.getCapabilities();
assert.equal(caps.atomicReplacement, true);
assert.equal(caps.revisions, false);
await storage.connect();
const read = await storage.readObject(DEFAULT_ENCRYPTED_SYNC_OBJECT_KEY);
assert.equal(read.found, true);
assert.ok(read.bytes);
const text = new TextDecoder().decode(read.bytes!);
assert.match(text, /remote-cipher/);
const next: SyncedFile = {
meta: {
version: 3,
updatedAt: 2,
deviceId: 'd',
appVersion: '0.0.0',
iv: 'iv',
salt: 'salt',
algorithm: 'AES-256-GCM',
kdf: 'PBKDF2',
},
payload: 'next-cipher',
};
await storage.writeObject(
DEFAULT_ENCRYPTED_SYNC_OBJECT_KEY,
new TextEncoder().encode(JSON.stringify(next)),
);
assert.equal(raw.store?.payload, 'next-cipher');
});
});