UNPKG

@sync-in/server

Version:

The secure, open-source platform for file storage, sharing, collaboration, and sync

424 lines (423 loc) 15.2 kB
/* * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com> * This file is part of Sync-in | The open source file sync and share solution * See the LICENSE file for licensing details */ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const _common = require("@nestjs/common"); const _testing = require("@nestjs/testing"); const _files = require("../../files/utils/files"); const _spaces = require("../../spaces/constants/spaces"); const _spacesbrowserservice = require("../../spaces/services/spaces-browser.service"); const _spacesmanagerservice = require("../../spaces/services/spaces-manager.service"); const _permissions = require("../../spaces/utils/permissions"); const _routes = require("../constants/routes"); const _webdav = require("../constants/webdav"); const _webdavspacesservice = require("./webdav-spaces.service"); // mocks for file utils and permissions jest.mock('../../files/utils/files', ()=>({ getProps: jest.fn(), isPathExists: jest.fn(), isPathIsDir: jest.fn() })); jest.mock('../../spaces/utils/permissions', ()=>({ canAccessToSpaceUrl: jest.fn() })); // mock for WEBDAV path-to-space segments jest.mock('../utils/routes', ()=>({ WEBDAV_PATH_TO_SPACE_SEGMENTS: jest.fn(()=>[ 'files', 'personal' ]) })); // small helper to collect results from an AsyncGenerator async function collectGenerator(gen) { const all = []; for await (const item of gen){ all.push(item); } return all; } describe(_webdavspacesservice.WebDAVSpaces.name, ()=>{ let service; let spacesManager; let spacesBrowser; const user = { id: 1, login: 'john', isAdmin: false }; beforeAll(async ()=>{ const spacesManagerMock = { spaceEnv: jest.fn(), listSpaces: jest.fn(), listTrashes: jest.fn() }; const spacesBrowserMock = { browse: jest.fn() }; const module = await _testing.Test.createTestingModule({ providers: [ { provide: _spacesbrowserservice.SpacesBrowser, useValue: spacesBrowserMock }, { provide: _spacesmanagerservice.SpacesManager, useValue: spacesManagerMock }, _webdavspacesservice.WebDAVSpaces ] }).compile(); module.useLogger([ 'fatal' ]); service = module.get(_webdavspacesservice.WebDAVSpaces); spacesManager = module.get(_spacesmanagerservice.SpacesManager); spacesBrowser = module.get(_spacesbrowserservice.SpacesBrowser); }); beforeEach(()=>{ jest.clearAllMocks(); }); it('should be defined', ()=>{ expect(service).toBeDefined(); }); describe('spaceEnv', ()=>{ it('returns space when manager resolves', async ()=>{ const fakeSpace = { alias: 'personal', id: 0 }; spacesManager.spaceEnv.mockResolvedValue(fakeSpace); const res = await service.spaceEnv(user, '/webdav/personal'); expect(res).toBe(fakeSpace); expect(spacesManager.spaceEnv).toHaveBeenCalled(); }); it('returns null when manager throws', async ()=>{ ; spacesManager.spaceEnv.mockRejectedValue(new Error('boom')); const res = await service.spaceEnv(user, '/webdav/personal'); expect(res).toBeNull(); }); it('returns null when space not found', async ()=>{ ; spacesManager.spaceEnv.mockResolvedValue(null); const res = await service.spaceEnv(user, '/webdav/personal'); expect(res).toBeNull(); }); }); describe('propfind - server root', ()=>{ it('yields server root and webdav child when depth=1', async ()=>{ const req = { user, dav: { url: '/', depth: _webdav.DEPTH.MEMBERS } }; const items = await collectGenerator(service.propfind(req, _routes.WEBDAV_NS.SERVER)); expect(items.map((i)=>i.name)).toEqual([ _routes.WEBDAV_NS.SERVER, _routes.WEBDAV_NS.WEBDAV ]); }); it('yields only server root when depth=0', async ()=>{ const req = { user, dav: { url: '/', depth: _webdav.DEPTH.RESOURCE } }; const items = await collectGenerator(service.propfind(req, _routes.WEBDAV_NS.SERVER)); expect(items.map((i)=>i.name)).toEqual([ _routes.WEBDAV_NS.SERVER ]); }); }); describe('propfind - webdav listing', ()=>{ it('filters repositories by user access', async ()=>{ ; _permissions.canAccessToSpaceUrl.mockImplementation((_u, repos)=>repos?.includes(_spaces.SPACE_REPOSITORY.FILES)); const req = { user, dav: { url: '/webdav', depth: _webdav.DEPTH.MEMBERS } }; const items = await collectGenerator(service.propfind(req, _routes.WEBDAV_NS.WEBDAV)); // should list only roots that include FILES in their repository (personal, spaces), not server/webdav expect(items.map((i)=>i.name)).toEqual([ _routes.WEBDAV_NS.WEBDAV, 'personal', 'spaces' ]); }); it('lists only itself when depth=0', async ()=>{ const req = { user, dav: { url: '/webdav', depth: _webdav.DEPTH.RESOURCE } }; const items = await collectGenerator(service.propfind(req, _routes.WEBDAV_NS.WEBDAV)); expect(items.map((i)=>i.name)).toEqual([ _routes.WEBDAV_NS.WEBDAV ]); }); }); describe('propfind - spaces listing', ()=>{ it('yields spaces root then user spaces when depth=1', async ()=>{ const now = new Date(); spacesManager.listSpaces.mockResolvedValue([ { id: 10, name: 'Team A', alias: 'team-a', createdAt: now.toISOString(), modifiedAt: now.toISOString() }, { id: 11, name: 'Team B', alias: 'team-b', createdAt: now.toISOString(), modifiedAt: now.toISOString() } ]); const req = { user, dav: { url: '/webdav/spaces', depth: _webdav.DEPTH.MEMBERS } }; const items = await collectGenerator(service.propfind(req, _routes.WEBDAV_NS.SPACES)); expect(items.map((i)=>i.name)).toEqual([ _routes.WEBDAV_NS.SPACES, 'Team A', 'Team B' ]); }); it('yields only spaces root when depth=0', async ()=>{ ; spacesManager.listSpaces.mockResolvedValue([]); const req = { user, dav: { url: '/webdav/spaces', depth: _webdav.DEPTH.RESOURCE } }; const items = await collectGenerator(service.propfind(req, _routes.WEBDAV_NS.SPACES)); expect(items.map((i)=>i.name)).toEqual([ _routes.WEBDAV_NS.SPACES ]); }); }); describe('propfind - trashes listing', ()=>{ it('yields trash root then each trash bucket when depth=1', async ()=>{ ; spacesManager.listTrashes.mockResolvedValue([ { id: 1, alias: 'personal', nb: 2, mtime: 3, ctime: 4, name: 'Personal files' }, { id: 2, alias: 'team-a', nb: 5, mtime: 6, ctime: 7, name: 'Team A' } ]); const req = { user, dav: { url: '/webdav/trash', depth: _webdav.DEPTH.MEMBERS } }; const items = await collectGenerator(service.propfind(req, _routes.WEBDAV_NS.TRASH)); expect(items.map((i)=>i.name)).toEqual([ _routes.WEBDAV_NS.TRASH, 'personal (2)', 'team-a (5)' ]); }); it('yields only trash root when depth=0', async ()=>{ ; spacesManager.listTrashes.mockResolvedValue([]); const req = { user, dav: { url: '/webdav/trash', depth: _webdav.DEPTH.RESOURCE } }; const items = await collectGenerator(service.propfind(req, _routes.WEBDAV_NS.TRASH)); expect(items.map((i)=>i.name)).toEqual([ _routes.WEBDAV_NS.TRASH ]); }); }); describe('propfind - files listing (shares list)', ()=>{ it('yields shares root and children when inSharesList=true and depth=1', async ()=>{ ; spacesBrowser.browse.mockResolvedValue({ files: [ { id: 1, name: 'doc.txt', isDir: false, size: 12, ctime: 1, mtime: 2, mime: 'text/plain' } ] }); const req = { user, dav: { url: '/webdav/files/personal', depth: _webdav.DEPTH.MEMBERS }, space: { inSharesList: true, realPath: '/any/ignored' } }; const items = await collectGenerator(service.propfind(req, _spaces.SPACE_REPOSITORY.FILES)); expect(items.map((i)=>i.name)).toEqual([ 'shares', 'doc.txt' ]); }); }); describe('propfind - files listing (path cases)', ()=>{ it('throws 404 when path does not exist', async ()=>{ ; _files.isPathExists.mockResolvedValue(false); const req = { user, dav: { url: '/webdav/files/personal/current', depth: _webdav.DEPTH.RESOURCE }, space: { inSharesList: false, realPath: '/path/not/found' } }; await expect(collectGenerator(service.propfind(req, _spaces.SPACE_REPOSITORY.FILES))).rejects.toBeInstanceOf(_common.HttpException); await expect(collectGenerator(service.propfind(req, _spaces.SPACE_REPOSITORY.FILES))).rejects.toMatchObject({ status: _common.HttpStatus.NOT_FOUND }); }); it('yields current directory and children when path exists and is dir', async ()=>{ ; _files.isPathExists.mockResolvedValue(true); _files.getProps.mockResolvedValue({ id: 100, name: 'current', isDir: true, size: 0, ctime: Date.now(), mtime: Date.now(), mime: undefined }); _files.isPathIsDir.mockResolvedValue(true); spacesBrowser.browse.mockResolvedValue({ files: [ { id: 2, name: 'child.txt', isDir: false, size: 1, ctime: 1, mtime: 2, mime: 'text/plain' } ] }); const req = { user, dav: { url: '/webdav/files/personal/current', depth: _webdav.DEPTH.MEMBERS }, space: { inSharesList: false, realPath: '/path/current' } }; const items = await collectGenerator(service.propfind(req, _spaces.SPACE_REPOSITORY.FILES)); expect(items.map((i)=>i.name)).toEqual([ 'current', 'child.txt' ]); }); it('does not list children when path exists but is not a directory', async ()=>{ ; _files.isPathExists.mockResolvedValue(true); _files.getProps.mockResolvedValue({ id: 101, name: 'current', isDir: false, size: 123, ctime: Date.now(), mtime: Date.now(), mime: 'text/plain' }); _files.isPathIsDir.mockResolvedValue(false); spacesBrowser.browse.mockResolvedValue({ files: [] }); const req = { user, dav: { url: '/webdav/files/personal/current', depth: _webdav.DEPTH.MEMBERS }, space: { inSharesList: false, realPath: '/path/current' } }; const items = await collectGenerator(service.propfind(req, _spaces.SPACE_REPOSITORY.FILES)); expect(items.map((i)=>i.name)).toEqual([ 'current' ]); expect(spacesBrowser.browse).not.toHaveBeenCalled(); }); }); describe('propfind - unknown space', ()=>{ it('throws not found for unknown space', ()=>{ const req = { user, dav: { url: '/webdav/unknown', depth: _webdav.DEPTH.RESOURCE } }; try { service.propfind(req, 'unknown'); fail('Expected HttpException to be thrown'); } catch (err) { expect(err).toBeInstanceOf(_common.HttpException); expect(err.getStatus()).toBe(_common.HttpStatus.NOT_FOUND); } }); }); }); //# sourceMappingURL=webdav-spaces.service.spec.js.map