From 65c8733ea7ae5a76b52d69836bb0aaaa5c94b955 Mon Sep 17 00:00:00 2001 From: Pixo Date: Fri, 8 Mar 2024 13:06:17 +0100 Subject: [PATCH 01/13] [PB-1338]:(feature) List WebDav content using RealmDB to match by relative path --- package.json | 1 + src/services/config.service.ts | 1 + src/services/realms/drive-files.realm.ts | 49 ++++ src/services/realms/drive-folders.realm.ts | 69 ++++++ .../realms/drive-realm-manager.service.ts | 43 ++++ src/types/drive.types.ts | 2 +- src/utils/xml.utils.ts | 2 +- src/webdav/handlers/PROPFIND.handler.ts | 213 ++++++++++-------- src/webdav/index.ts | 22 +- .../middewares/request-logger.middleware.ts | 2 + src/webdav/webdav-server.ts | 13 +- test/fixtures/drive-realm.fixture.ts | 66 ++++++ .../drive-realm-manager.service.test.ts | 45 ++++ test/webdav/handlers/PROPFIND.handler.test.ts | 28 ++- .../request-logger.middleware.test.ts | 4 +- test/webdav/webdav-server.test.ts | 3 +- yarn.lock | 164 +++++++++++++- 17 files changed, 621 insertions(+), 106 deletions(-) create mode 100644 src/services/realms/drive-files.realm.ts create mode 100644 src/services/realms/drive-folders.realm.ts create mode 100644 src/services/realms/drive-realm-manager.service.ts create mode 100644 test/fixtures/drive-realm.fixture.ts create mode 100644 test/services/realms/drive-realm-manager.service.test.ts diff --git a/package.json b/package.json index de6408d..53c2ea9 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "express-basic-auth": "^1.2.1", "fast-xml-parser": "^4.3.5", "openpgp": "^5.11.1", + "realm": "^12.6.2", "superagent": "^8.1.2", "winston": "^3.12.0" }, diff --git a/src/services/config.service.ts b/src/services/config.service.ts index e80d769..1e50871 100644 --- a/src/services/config.service.ts +++ b/src/services/config.service.ts @@ -7,6 +7,7 @@ import { CryptoService } from './crypto.service'; export class ConfigService { static readonly CREDENTIALS_FILE = path.join(os.homedir(), '.inxtcli'); + static readonly DRIVE_REALM_FILE = path.join(os.homedir(), 'internxt-cli-drive.realm'); public static readonly instance: ConfigService = new ConfigService(); /** diff --git a/src/services/realms/drive-files.realm.ts b/src/services/realms/drive-files.realm.ts new file mode 100644 index 0000000..0d256da --- /dev/null +++ b/src/services/realms/drive-files.realm.ts @@ -0,0 +1,49 @@ +import Realm, { ObjectSchema } from 'realm'; + +export class DriveFileRealmSchema extends Realm.Object { + id!: number; + name!: string; + type?: string; + uuid!: string; + fileId!: string; + folder_id!: number; + folder_uuid!: string; + bucket!: string; + relative_path!: string; + created_at!: Date; + updated_at!: Date; + size!: number; + status!: 'EXISTS' | 'REMOVED' | 'TRASHED'; + static schema: ObjectSchema = { + name: 'DriveFile', + properties: { + id: 'int', + name: 'string', + type: 'string?', + uuid: { type: 'string', indexed: true }, + file_id: 'string', + folder_id: 'int', + folder_uuid: 'string', + bucket: 'string', + relative_path: { type: 'string', indexed: true }, + created_at: 'date', + updated_at: 'date', + size: 'int', + status: 'string', + }, + primaryKey: 'id', + }; +} + +export class DriveFilesRealm { + constructor(private realm: Realm) {} + + async getByRelativePath(relativePath: string): Promise { + const object = this.realm + .objects('DriveFile') + .filtered('relative_path = $0', relativePath) + .find((file) => file.relative_path === relativePath); + + return object ?? null; + } +} diff --git a/src/services/realms/drive-folders.realm.ts b/src/services/realms/drive-folders.realm.ts new file mode 100644 index 0000000..99a62aa --- /dev/null +++ b/src/services/realms/drive-folders.realm.ts @@ -0,0 +1,69 @@ +import Realm, { ObjectSchema } from 'realm'; +import { DriveFolderItem } from '../../types/drive.types'; + +export class DriveFolderRealmSchema extends Realm.Object { + id!: number; + name!: string; + uuid!: string; + relative_path!: string; + parent_id?: number; + created_at!: Date; + updated_at!: Date; + status!: 'EXISTS' | 'REMOVED' | 'TRASHED'; + static schema: ObjectSchema = { + name: 'DriveFolder', + properties: { + id: 'int', + name: 'string', + uuid: { type: 'string', indexed: true }, + relative_path: { type: 'string', indexed: true }, + parent_id: { type: 'int', indexed: true, default: -1 }, + created_at: 'date', + updated_at: 'date', + status: 'string', + }, + primaryKey: 'id', + }; +} + +export class DriveFoldersRealm { + constructor(private realm: Realm) {} + + async findByRelativePath(relativePath: string): Promise { + const object = this.realm + .objects('DriveFolder') + .filtered('relative_path = $0', relativePath) + .find((folder) => folder.relative_path === relativePath); + + return object ?? null; + } + + async findByParentId(parentId: number | null): Promise { + // -1 is root as we cannot index null fields + const parentFolder = this.realm.objectForPrimaryKey('DriveFolder', parentId ?? -1); + + return parentFolder; + } + + async create(driveFolder: DriveFolderItem, relativePath: string) { + const exists = this.realm.objectForPrimaryKey('DriveFolder', driveFolder.id); + + this.realm.write(() => { + if (exists) { + this.realm.delete(exists); + } + this.realm.create('DriveFolder', { + id: driveFolder.id, + name: driveFolder.name, + uuid: driveFolder.uuid, + parent_id: driveFolder.parentId ?? -1, + created_at: driveFolder.createdAt, + updated_at: driveFolder.updatedAt, + status: 'EXISTS', + relative_path: relativePath, + }); + }); + + return; + } +} diff --git a/src/services/realms/drive-realm-manager.service.ts b/src/services/realms/drive-realm-manager.service.ts new file mode 100644 index 0000000..e925d27 --- /dev/null +++ b/src/services/realms/drive-realm-manager.service.ts @@ -0,0 +1,43 @@ +import path from 'path'; +import { DriveFileItem, DriveFolderItem } from '../../types/drive.types'; +import { DriveFilesRealm } from './drive-files.realm'; +import { DriveFoldersRealm } from './drive-folders.realm'; + +export class DriveRealmManager { + constructor( + private driveFilesRealm: DriveFilesRealm, + private driveFoldersRealm: DriveFoldersRealm, + ) {} + + async findByRelativePath(relativePath: string) { + const driveFile = await this.driveFilesRealm.getByRelativePath(relativePath); + + if (driveFile) return driveFile; + + const driveFolder = await this.driveFoldersRealm.findByRelativePath(relativePath); + + if (driveFolder) return driveFolder; + + return null; + } + + async createFolder(driveFolder: DriveFolderItem) { + const relativePath = await this.buildRelativePathForFolder(driveFolder.name, driveFolder.parentId ?? null); + + return this.driveFoldersRealm.create(driveFolder, relativePath); + } + + async buildRelativePathForFolder(folderName: string, parentId: number | null): Promise { + const parentFolder = await this.driveFoldersRealm.findByParentId(parentId); + + if (!parentFolder) { + return path.join('/', folderName, '/'); + } + + const parentPath = await this.buildRelativePathForFolder(parentFolder.name, parentFolder.parent_id ?? null); + + return path.join(parentPath, folderName, '/'); + } + + async close() {} +} diff --git a/src/types/drive.types.ts b/src/types/drive.types.ts index 629c38a..c3c7b3c 100644 --- a/src/types/drive.types.ts +++ b/src/types/drive.types.ts @@ -7,7 +7,7 @@ export type DriveFileItem = Pick & { +export type DriveFolderItem = Pick & { encryptedName: string; uuid: string; createdAt: Date; diff --git a/src/utils/xml.utils.ts b/src/utils/xml.utils.ts index ea49eec..2a4fcf2 100644 --- a/src/utils/xml.utils.ts +++ b/src/utils/xml.utils.ts @@ -13,6 +13,6 @@ export class XMLUtils { static toWebDavXML(object: Record, options: XmlBuilderOptions) { const xmlContent = this.toXML(object, options); - return `${xmlContent}`; + return `${xmlContent}`; } } diff --git a/src/webdav/handlers/PROPFIND.handler.ts b/src/webdav/handlers/PROPFIND.handler.ts index 1b6f093..cb4b283 100644 --- a/src/webdav/handlers/PROPFIND.handler.ts +++ b/src/webdav/handlers/PROPFIND.handler.ts @@ -5,56 +5,84 @@ import path, { ParsedPath } from 'path'; import { DriveFolderService } from '../../services/drive/drive-folder.service'; import { FormatUtils } from '../../utils/format.utils'; import { Request, Response } from 'express'; +import { DriveRealmManager } from '../../services/realms/drive-realm-manager.service'; export type WebDavRequestedResource = { type: 'file' | 'folder' | 'root'; + url: string; name: string; path: ParsedPath; }; export class PROPFINDRequestHandler implements WebDavMethodHandler { constructor( private options: WebDavMethodHandlerOptions = { debug: false }, - private dependencies: { driveFolderService: DriveFolderService }, + private dependencies: { driveFolderService: DriveFolderService; driveRealmManager: DriveRealmManager }, ) {} handle = async (req: Request, res: Response) => { const resource = this.getRequestedResource(req); switch (resource.type) { - case 'root': - res.status(200).send(await this.getRootFolderContentXML(req.user)); + case 'root': { + const rootFolder = await this.dependencies.driveFolderService.getFolderMetaById(req.user.rootFolderId); + res.status(200).send(await this.getFolderContentXML('/', rootFolder.uuid)); break; - case 'file': + } + case 'file': { + const folderPath = path.join(path.dirname(resource.url), '/'); + const driveParentFolder = await this.dependencies.driveRealmManager.findByRelativePath( + decodeURIComponent(folderPath), + ); + + if (!driveParentFolder) { + res.status(404).send(); + return; + } + res.status(200).send(await this.getFileMetaXML(resource)); break; - case 'folder': - res.status(200).send(await this.getFolderMetaXML(resource)); + } + + case 'folder': { + const driveParentFolder = await this.dependencies.driveRealmManager.findByRelativePath( + decodeURIComponent(resource.url), + ); + + if (!driveParentFolder) { + res.status(404).send(); + return; + } + + res.status(200).send(await this.getFolderContentXML(resource.url, driveParentFolder.uuid)); break; + } } }; private getRequestedResource(req: Request): WebDavRequestedResource { const parsedPath = path.parse(req.url); + // This is the root of the WebDav folder - if (req.url === '/webdav' || req.url === '/webdav/') { + if (req.url === '/') { return { type: 'root', name: 'root', + url: req.url, path: parsedPath, }; } - // Assume this is a file - if (parsedPath.ext) { + if (req.url.endsWith('/')) { return { - type: 'file', + url: req.url, + type: 'folder', name: parsedPath.name, path: parsedPath, }; - // Otherwise, this is a folder } else { return { - type: 'folder', + type: 'file', + url: req.url, name: parsedPath.name, path: parsedPath, }; @@ -63,89 +91,98 @@ export class PROPFINDRequestHandler implements WebDavMethodHandler { private async getFileMetaXML(resource: WebDavRequestedResource): Promise { // For now this is mocked data - const driveFile = this.driveFileItemToXMLNode({ - name: resource.path.name, - type: resource.path.ext.slice(1), - bucket: '', - id: 0, - uuid: '', - fileId: '', - encryptedName: '', - size: 0, - createdAt: new Date(), - updatedAt: new Date(), - }); + const driveFile = this.driveFileItemToXMLNode( + { + name: resource.path.name, + type: resource.path.ext.slice(1), + bucket: '', + id: 0, + uuid: '', + fileId: '', + encryptedName: '', + size: 0, + createdAt: new Date(), + updatedAt: new Date(), + }, + resource.url, + ); return XMLUtils.toWebDavXML([driveFile], { - arrayNodeName: 'D:response', + arrayNodeName: 'response', }); } - private async getFolderMetaXML(resource: WebDavRequestedResource): Promise { - // For now this is mocked data - const driveFile = this.driveFolderItemToXMLNode({ - name: resource.name, - bucket: '', - createdAt: new Date(), - updatedAt: new Date(), - id: 0, - encryptedName: '', - uuid: '', - }); - return XMLUtils.toWebDavXML([driveFile], { - arrayNodeName: 'D:response', + private async getFolderContentXML(relativePath: string, folderUuid: string) { + const { driveFolderService, driveRealmManager } = this.dependencies; + + const folderContent = await driveFolderService.getFolderContent(folderUuid); + + const foldersXML = folderContent.folders.map((folder) => { + const folderRelativePath = path.join(relativePath, encodeURIComponent(folder.plainName), '/'); + + return this.driveFolderItemToXMLNode( + { + name: folder.plainName, + bucket: folder.bucket, + createdAt: new Date(folder.createdAt), + updatedAt: new Date(folder.updatedAt), + id: folder.id, + encryptedName: folder.name, + uuid: folder.uuid, + parentId: null, + }, + folderRelativePath, + ); }); - } - private async getRootFolderContentXML(user: Request['user']) { - const { driveFolderService } = this.dependencies; - const rootFolder = await driveFolderService.getFolderMetaById(user.rootFolderId); - - const folderContent = await driveFolderService.getFolderContent(rootFolder.uuid); - - const foldersXML = folderContent.folders.map((folder) => - this.driveFolderItemToXMLNode({ - name: folder.plainName, - bucket: folder.bucket, - createdAt: new Date(folder.createdAt), - updatedAt: new Date(folder.updatedAt), - id: folder.id, - encryptedName: folder.name, - uuid: folder.uuid, + await Promise.all( + folderContent.folders.map(async (folder) => { + return driveRealmManager.createFolder({ + ...folder, + name: folder.plainName, + encryptedName: folder.name, + }); }), ); - const filesXML = folderContent.files.map((file) => - this.driveFileItemToXMLNode({ - name: file.plainName, - bucket: file.bucket, - id: file.id, - createdAt: new Date(file.createdAt), - updatedAt: new Date(file.updatedAt), - fileId: file.fileId, - uuid: file.uuid, - type: file.type, - encryptedName: file.name, - size: Number(file.size), - }), - ); + const filesXML = folderContent.files.map((file) => { + const fileRelativePath = path.join(relativePath, encodeURIComponent(file.plainName)); + return this.driveFileItemToXMLNode( + { + name: file.plainName, + bucket: file.bucket, + id: file.id, + createdAt: new Date(file.createdAt), + updatedAt: new Date(file.updatedAt), + fileId: file.fileId, + uuid: file.uuid, + type: file.type, + encryptedName: file.name, + size: Number(file.size), + }, + fileRelativePath, + ); + }); - return XMLUtils.toWebDavXML(foldersXML.concat(filesXML), { - arrayNodeName: 'D:response', + const xml = XMLUtils.toWebDavXML(foldersXML.concat(filesXML), { + arrayNodeName: 'response', }); + + return xml; } - private driveFolderItemToXMLNode(driveFolderItem: DriveFolderItem): object { + private driveFolderItemToXMLNode(driveFolderItem: DriveFolderItem, relativePath: string): object { const displayName = `${driveFolderItem.name}`; + const driveFolderXML = { - 'D:href': path.join('/webdav', encodeURIComponent(displayName)), - 'D:propstat': { - 'D:status': 'HTTP/1.1 200 OK', - 'D:prop': { - 'D:displayname': displayName, - 'D:getlastmodified': FormatUtils.formatDateForWebDav(driveFolderItem.updatedAt), - 'D:getcontentlength': 0, - 'D:resourcetype': { - 'D:collection': '', + href: relativePath, + propstat: { + status: 'HTTP/1.1 200 OK', + prop: { + displayname: displayName, + getlastmodified: FormatUtils.formatDateForWebDav(driveFolderItem.updatedAt), + getcontentlength: 0, + resourcetype: { + collection: '', }, }, }, @@ -154,17 +191,17 @@ export class PROPFINDRequestHandler implements WebDavMethodHandler { return driveFolderXML; } - private driveFileItemToXMLNode(driveFileItem: DriveFileItem): object { + private driveFileItemToXMLNode(driveFileItem: DriveFileItem, relativePath: string): object { const displayName = driveFileItem.type ? `${driveFileItem.name}.${driveFileItem.type}` : driveFileItem.name; const driveFileXML = { - 'D:href': path.join('/webdav', encodeURIComponent(displayName)), - 'D:propstat': { - 'D:status': 'HTTP/1.1 200 OK', - 'D:prop': { - 'D:displayname': displayName, - 'D:getlastmodified': FormatUtils.formatDateForWebDav(driveFileItem.updatedAt), - 'D:getcontentlength': driveFileItem.size, + href: relativePath, + propstat: { + status: 'HTTP/1.1 200 OK', + prop: { + displayname: displayName, + getlastmodified: FormatUtils.formatDateForWebDav(driveFileItem.updatedAt), + getcontentlength: driveFileItem.size, }, }, }; diff --git a/src/webdav/index.ts b/src/webdav/index.ts index 9d15611..7231921 100644 --- a/src/webdav/index.ts +++ b/src/webdav/index.ts @@ -1,9 +1,29 @@ import dotenv from 'dotenv'; import { WebDavServer } from './webdav-server'; import express from 'express'; +import { Realm } from 'realm'; import { ConfigService } from '../services/config.service'; import { DriveFolderService } from '../services/drive/drive-folder.service'; +import { DriveRealmManager } from '../services/realms/drive-realm-manager.service'; +import { DriveFilesRealm, DriveFileRealmSchema } from '../services/realms/drive-files.realm'; +import { DriveFoldersRealm, DriveFolderRealmSchema } from '../services/realms/drive-folders.realm'; dotenv.config(); -new WebDavServer(express(), ConfigService.instance, DriveFolderService.instance).start().then().catch(console.error); +const init = async () => { + const realm = await Realm.open({ + path: ConfigService.DRIVE_REALM_FILE, + schema: [DriveFileRealmSchema, DriveFolderRealmSchema], + }); + new WebDavServer( + express(), + ConfigService.instance, + DriveFolderService.instance, + new DriveRealmManager(new DriveFilesRealm(realm), new DriveFoldersRealm(realm)), + ) + .start() + .then() + .catch(console.error); +}; + +init(); diff --git a/src/webdav/middewares/request-logger.middleware.ts b/src/webdav/middewares/request-logger.middleware.ts index b5fdbd0..e788fd4 100644 --- a/src/webdav/middewares/request-logger.middleware.ts +++ b/src/webdav/middewares/request-logger.middleware.ts @@ -2,10 +2,12 @@ import { RequestHandler } from 'express'; import { webdavLogger } from '../../utils/logger.utils'; type RequestLoggerConfig = { + enable: boolean; methods?: string[]; }; export const RequestLoggerMiddleware = (config: RequestLoggerConfig): RequestHandler => { return (req, _, next) => { + if (!config.enable) return next(); if (config.methods && !config.methods.includes(req.method)) return next(); webdavLogger.info( `WebDav request received\nMethod: ${req.method}\nURL: ${req.url}\nBody: ${req.body}\nHeaders: ${JSON.stringify(req.headers)}`, diff --git a/src/webdav/webdav-server.ts b/src/webdav/webdav-server.ts index 3165331..728f4e2 100644 --- a/src/webdav/webdav-server.ts +++ b/src/webdav/webdav-server.ts @@ -7,28 +7,35 @@ import bodyParser from 'body-parser'; import { DriveFolderService } from '../services/drive/drive-folder.service'; import { AuthMiddleware } from './middewares/auth.middleware'; import { RequestLoggerMiddleware } from './middewares/request-logger.middleware'; +import { DriveRealmManager } from '../services/realms/drive-realm-manager.service'; export class WebDavServer { constructor( private app: Express, private configService: ConfigService, private driveFolderService: DriveFolderService, + private driveRealmManager: DriveRealmManager, ) {} private registerMiddlewares = () => { this.app.use(bodyParser.text({ type: ['application/xml', 'text/xml'] })); - this.app.use(RequestLoggerMiddleware({})); + this.app.use( + RequestLoggerMiddleware({ + enable: false, + }), + ); this.app.use(AuthMiddleware(ConfigService.instance)); }; private registerHandlers = () => { - this.app.options('/webdav', new OPTIONSRequestHandler().handle); + this.app.options('*', new OPTIONSRequestHandler().handle); this.app.propfind( - '/webdav', + '*', new PROPFINDRequestHandler( { debug: true }, { driveFolderService: this.driveFolderService, + driveRealmManager: this.driveRealmManager, }, ).handle, ); diff --git a/test/fixtures/drive-realm.fixture.ts b/test/fixtures/drive-realm.fixture.ts new file mode 100644 index 0000000..79a0c0c --- /dev/null +++ b/test/fixtures/drive-realm.fixture.ts @@ -0,0 +1,66 @@ +import sinon from 'sinon'; +import { DriveFileRealmSchema, DriveFilesRealm } from '../../src/services/realms/drive-files.realm'; +import { DriveFolderRealmSchema, DriveFoldersRealm } from '../../src/services/realms/drive-folders.realm'; +import { DriveRealmManager } from '../../src/services/realms/drive-realm-manager.service'; + +export const getDriveFileRealmSchemaFixture = (payload: Partial = {}): DriveFolderRealmSchema => { + // @ts-expect-error - We only mock the properties we need + const object: DriveFileRealmSchema = { + id: new Date().getTime(), + name: `file_${new Date().getTime().toString()}`, + uuid: `uuid_${new Date().getTime().toString()}`, + relative_path: '', + created_at: new Date(), + updated_at: new Date(), + status: 'EXISTS', + fileId: `file_id_${new Date().getTime().toString()}`, + folder_id: 0, + folder_uuid: `folder_uuid_${new Date().getTime().toString()}`, + bucket: new Date().getTime().toString(), + size: 0, + }; + + // @ts-expect-error - We only mock the properties we need + return { + ...object, + ...payload, + }; +}; + +export const getDriveFolderRealmSchemaFixture = ( + payload: Partial = {}, +): DriveFolderRealmSchema => { + // @ts-expect-error - We only mock the properties we need + const object: DriveFolderRealmSchema = { + id: new Date().getTime(), + name: `folder_${new Date().getTime().toString()}`, + uuid: `uuid_${new Date().getTime().toString()}`, + relative_path: '', + created_at: new Date(), + updated_at: new Date(), + status: 'EXISTS', + }; + + // @ts-expect-error - We only mock the properties we need + return { + ...object, + ...payload, + }; +}; + +export const getDriveRealmManager = (): DriveRealmManager => { + // @ts-expect-error - We only mock the properties we need + const driveFilesRealm: DriveFilesRealm = { + getByRelativePath: sinon.stub(), + }; + + // @ts-expect-error - We only mock the properties we need + const driveFoldersRealm: DriveFoldersRealm = { + findByRelativePath: sinon.stub(), + findByParentId: async () => null, + create: async () => { + return; + }, + }; + return new DriveRealmManager(driveFilesRealm, driveFoldersRealm); +}; diff --git a/test/services/realms/drive-realm-manager.service.test.ts b/test/services/realms/drive-realm-manager.service.test.ts new file mode 100644 index 0000000..99c7ffe --- /dev/null +++ b/test/services/realms/drive-realm-manager.service.test.ts @@ -0,0 +1,45 @@ +import sinon from 'sinon'; +import { DriveFilesRealm } from '../../../src/services/realms/drive-files.realm'; +import { DriveFoldersRealm } from '../../../src/services/realms/drive-folders.realm'; +import { DriveRealmManager } from '../../../src/services/realms/drive-realm-manager.service'; +import { getDriveFolderRealmSchemaFixture } from '../../fixtures/drive-realm.fixture'; +import { expect } from 'chai'; +describe('DriveRealmManager service', () => { + const sandbox = sinon.createSandbox(); + afterEach(() => { + sandbox.restore(); + }); + it('When a folder is created, should build the correct relative path', async () => { + // @ts-expect-error - We only mock the properties we need + const driveFilesRealm: DriveFilesRealm = { + getByRelativePath: sandbox.stub(), + }; + + // @ts-expect-error - We only mock the properties we need + const driveFoldersRealm: DriveFoldersRealm = { + findByRelativePath: sandbox.stub(), + findByParentId: async () => null, + }; + + sandbox.stub(driveFoldersRealm, 'findByParentId').callsFake(async (parentId: number | null) => { + if (parentId === 1) { + return getDriveFolderRealmSchemaFixture({ id: parentId, name: 'folderC', parent_id: 2 }); + } + + if (parentId === 2) { + return getDriveFolderRealmSchemaFixture({ id: parentId, name: 'folderB', parent_id: 3 }); + } + + if (parentId === 3) { + return getDriveFolderRealmSchemaFixture({ id: parentId, name: 'folderA', parent_id: undefined }); + } + return null; + }); + + const sut = new DriveRealmManager(driveFilesRealm, driveFoldersRealm); + + const path = await sut.buildRelativePathForFolder('folderD', 1); + + expect(path).to.be.equal('/folderA/folderB/folderC/folderD/'); + }); +}); diff --git a/test/webdav/handlers/PROPFIND.handler.test.ts b/test/webdav/handlers/PROPFIND.handler.test.ts index 799680f..f84ce71 100644 --- a/test/webdav/handlers/PROPFIND.handler.test.ts +++ b/test/webdav/handlers/PROPFIND.handler.test.ts @@ -7,9 +7,16 @@ import { UserSettingsFixture } from '../../fixtures/auth.fixture'; import { newFolder, newPaginatedFolder } from '../../fixtures/drive.fixture'; import { createWebDavRequestFixture, createWebDavResponseFixture } from '../../fixtures/webdav.fixture'; import path from 'path'; +import { + getDriveFileRealmSchemaFixture, + getDriveFolderRealmSchemaFixture, + getDriveRealmManager, +} from '../../fixtures/drive-realm.fixture'; +import { get } from 'http'; describe('PROPFIND request handler', () => { const sandbox = sinon.createSandbox(); + afterEach(() => { sandbox.restore(); }); @@ -31,11 +38,12 @@ describe('PROPFIND request handler', () => { { debug: true }, { driveFolderService, + driveRealmManager: getDriveRealmManager(), }, ); const request = createWebDavRequestFixture({ - url: '/webdav', + url: '/', method: 'PROPFIND', user: UserSettingsFixture, }); @@ -50,7 +58,7 @@ describe('PROPFIND request handler', () => { sinon.assert.calledWith(response.status, 200); sinon.assert.calledWith( sendStub, - '', + '', ); }); @@ -80,11 +88,12 @@ describe('PROPFIND request handler', () => { { debug: true }, { driveFolderService, + driveRealmManager: getDriveRealmManager(), }, ); const request = createWebDavRequestFixture({ - url: '/webdav', + url: '/', method: 'PROPFIND', }); @@ -97,7 +106,7 @@ describe('PROPFIND request handler', () => { sinon.assert.calledWith(response.status, 200); sinon.assert.calledWith( sendStub, - `${path.join('/webdav', 'folder_1')}HTTP/1.1 200 OKfolder_1Mon, 04 Mar 2024 15:11:01 GMT0`, + `${path.join('/', 'folder_1', '/')}HTTP/1.1 200 OKfolder_1Mon, 04 Mar 2024 15:11:01 GMT0`, ); }); @@ -109,15 +118,19 @@ describe('PROPFIND request handler', () => { .stub(configService, 'readUser') .resolves({ user: UserSettingsFixture, token: 'TOKEN', newToken: 'NEW_TOKEN', mnemonic: 'MNEMONIC' }); + const driveRealmManager = getDriveRealmManager(); + sandbox.stub(driveRealmManager, 'findByRelativePath').resolves(getDriveFolderRealmSchemaFixture()); + const requestHandler = new PROPFINDRequestHandler( { debug: true }, { driveFolderService, + driveRealmManager, }, ); const request = createWebDavRequestFixture({ - url: '/webdav/file.png', + url: '/file.png', method: 'PROPFIND', }); @@ -139,15 +152,18 @@ describe('PROPFIND request handler', () => { .stub(configService, 'readUser') .resolves({ user: UserSettingsFixture, token: 'TOKEN', newToken: 'NEW_TOKEN', mnemonic: 'MNEMONIC' }); + const driveRealmManager = getDriveRealmManager(); + sandbox.stub(driveRealmManager, 'findByRelativePath').resolves(getDriveFolderRealmSchemaFixture()); const requestHandler = new PROPFINDRequestHandler( { debug: true }, { driveFolderService, + driveRealmManager, }, ); const request = createWebDavRequestFixture({ - url: '/webdav/folder_a', + url: '/folder_a', method: 'PROPFIND', }); diff --git a/test/webdav/middlewares/request-logger.middleware.test.ts b/test/webdav/middlewares/request-logger.middleware.test.ts index 8be2f29..d15f05f 100644 --- a/test/webdav/middlewares/request-logger.middleware.test.ts +++ b/test/webdav/middlewares/request-logger.middleware.test.ts @@ -17,7 +17,7 @@ describe('Request logger middleware', () => { const next = sandbox.spy(); const infoStub = sandbox.stub(webdavLogger, 'info'); - const middleware = RequestLoggerMiddleware({ methods: ['PROPFIND'] }); + const middleware = RequestLoggerMiddleware({ methods: ['PROPFIND'], enable: true }); middleware(req, createWebDavResponseFixture({}), next); expect(infoStub.calledOnce).to.be.true; @@ -33,7 +33,7 @@ describe('Request logger middleware', () => { const next = sandbox.spy(); const infoStub = sandbox.stub(webdavLogger, 'info'); - const middleware = RequestLoggerMiddleware({ methods: ['PROPFIND'] }); + const middleware = RequestLoggerMiddleware({ methods: ['PROPFIND'], enable: true }); middleware(req, createWebDavResponseFixture({}), next); expect(infoStub.notCalled).to.be.true; diff --git a/test/webdav/webdav-server.test.ts b/test/webdav/webdav-server.test.ts index 6ac25df..1a5d89b 100644 --- a/test/webdav/webdav-server.test.ts +++ b/test/webdav/webdav-server.test.ts @@ -4,6 +4,7 @@ import sinon from 'sinon'; import { ConfigService } from '../../src/services/config.service'; import { DriveFolderService } from '../../src/services/drive/drive-folder.service'; import { WebDavServer } from '../../src/webdav/webdav-server'; +import { getDriveRealmManager } from '../fixtures/drive-realm.fixture'; describe('WebDav server', () => { const sandbox = sinon.createSandbox(); @@ -13,7 +14,7 @@ describe('WebDav server', () => { it('When the WebDav server is started, should listen on the specified port', () => { const app = express(); - const server = new WebDavServer(app, ConfigService.instance, DriveFolderService.instance); + const server = new WebDavServer(app, ConfigService.instance, DriveFolderService.instance, getDriveRealmManager()); // @ts-expect-error - We are faking partially the listen method const listenStub = sandbox.stub(app, 'listen').callsFake((callback) => { diff --git a/yarn.lock b/yarn.lock index af9d0cc..29c0d7a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1806,6 +1806,11 @@ "@mattiasbuelens/web-streams-adapter" "~0.1.0" web-streams-polyfill "~3.0.3" +"@realm/fetch@^0.1.1": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@realm/fetch/-/fetch-0.1.1.tgz#1a637d0a1fc3734a7ffca64361d764777dd3d80c" + integrity sha512-hkTprw79RXGv54Je0DrjpQPLaz4QID2dO3FmthAQQWAkqwyrqMzrCGzJzLlmTKWZFsgLrN8KQyNewod27P+nJg== + "@sindresorhus/is@^4.0.0": version "4.6.0" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f" @@ -3537,7 +3542,7 @@ bip39@^3.0.2, bip39@^3.1.0: dependencies: "@noble/hashes" "^1.2.0" -bl@^4.1.0: +bl@^4.0.3, bl@^4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz" integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== @@ -3611,7 +3616,14 @@ browserslist@^4.22.2: node-releases "^2.0.14" update-browserslist-db "^1.0.13" -buffer@^5.5.0: +bson@^4.7.2: + version "4.7.2" + resolved "https://registry.yarnpkg.com/bson/-/bson-4.7.2.tgz#320f4ad0eaf5312dd9b45dc369cc48945e2a5f2e" + integrity sha512-Ry9wCtIZ5kGqkJoi6aD8KjxFZEx78guTQDnpXWiNthsxzrxAK/i8E6pCHAIZTbaEFWcOCvbecMukfK7XUvyLpQ== + dependencies: + buffer "^5.6.0" + +buffer@^5.5.0, buffer@^5.6.0: version "5.7.1" resolved "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz" integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== @@ -3865,6 +3877,11 @@ chokidar@^3.5.2: optionalDependencies: fsevents "~2.3.2" +chownr@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== + chownr@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" @@ -4349,6 +4366,11 @@ detect-indent@^7.0.1: resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-7.0.1.tgz#cbb060a12842b9c4d333f1cac4aa4da1bb66bc25" integrity sha512-Mc7QhQ8s+cLrnUfU/Ji94vG/r8M26m8f++vyres4ZoojaRDpZ1eSIh/EpzLNwlWuvzSZ3UbDFspjFvTDXe6e/g== +detect-libc@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.2.tgz#8ccf2ba9315350e1241b88d0ac3b0e1fbd99605d" + integrity sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw== + detect-newline@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-4.0.1.tgz#fcefdb5713e1fb8cb2839b8b6ee22e6716ab8f23" @@ -4460,7 +4482,7 @@ encoding@^0.1.12, encoding@^0.1.13: dependencies: iconv-lite "^0.6.2" -end-of-stream@^1.1.0: +end-of-stream@^1.1.0, end-of-stream@^1.4.1: version "1.4.4" resolved "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz" integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== @@ -4970,6 +4992,11 @@ execa@^5.0.0, execa@^5.1.1: signal-exit "^3.0.3" strip-final-newline "^2.0.0" +expand-template@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" + integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== + express-basic-auth@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/express-basic-auth/-/express-basic-auth-1.2.1.tgz#d31241c03a915dd55db7e5285573049cfcc36381" @@ -5299,6 +5326,11 @@ fromentries@^1.2.0: resolved "https://registry.yarnpkg.com/fromentries/-/fromentries-1.3.2.tgz#e4bca6808816bf8f93b52750f1127f5a6fd86e3a" integrity sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg== +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== + fs-extra@^8.1: version "8.1.0" resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz" @@ -5475,6 +5507,11 @@ git-hooks-list@^3.0.0: resolved "https://registry.yarnpkg.com/git-hooks-list/-/git-hooks-list-3.1.0.tgz#386dc531dcc17474cf094743ff30987a3d3e70fc" integrity sha512-LF8VeHeR7v+wAbXqfgRlTSX/1BJR9Q1vEMR8JAz1cEg6GX07+zyj3sAdDvYjj/xnlIfVuGgj4qBei1K3hKH+PA== +github-from-package@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" + integrity sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw== + github-slugger@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/github-slugger/-/github-slugger-1.5.0.tgz#17891bbc73232051474d68bd867a34625c955f7d" @@ -5877,6 +5914,11 @@ inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, i resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +ini@~1.3.0: + version "1.3.8" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + inquirer@^8.0.0: version "8.2.4" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-8.2.4.tgz#ddbfe86ca2f67649a67daa6f1051c128f684f0b4" @@ -6926,6 +6968,11 @@ minizlib@^2.0.0, minizlib@^2.1.1, minizlib@^2.1.2: minipass "^3.0.0" yallist "^4.0.0" +mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: + version "0.5.3" + resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" + integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== + mkdirp-infer-owner@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/mkdirp-infer-owner/-/mkdirp-infer-owner-2.0.0.tgz#55d3b368e7d89065c38f32fd38e638f0ab61d316" @@ -7002,6 +7049,11 @@ mute-stream@0.0.8: resolved "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz" integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== +napi-build-utils@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806" + integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg== + natural-compare-lite@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" @@ -7050,6 +7102,13 @@ nock@^13.5.1, nock@^13.5.4: json-stringify-safe "^5.0.1" propagate "^2.0.0" +node-abi@^3.3.0: + version "3.56.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.56.0.tgz#ca807d5ff735ac6bbbd684ae3ff2debc1c2a40a7" + integrity sha512-fZjdhDOeRcaS+rcpve7XuwHBmktS1nS1gzgghwKUQQ8nTy2FdSDr6ZT8k6YhvlJeHmmQMYiT/IH9hfco5zeW2Q== + dependencies: + semver "^7.3.5" + node-fetch@^2.6.7: version "2.6.7" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" @@ -7073,6 +7132,11 @@ node-gyp@^8.2.0: tar "^6.1.2" which "^2.0.2" +node-machine-id@^1.1.12: + version "1.1.12" + resolved "https://registry.yarnpkg.com/node-machine-id/-/node-machine-id-1.1.12.tgz#37904eee1e59b320bb9c5d6c0a59f3b469cb6267" + integrity sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ== + node-preload@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/node-preload/-/node-preload-0.2.1.tgz#c03043bb327f417a18fee7ab7ee57b408a144301" @@ -7623,6 +7687,11 @@ password-prompt@^1.1.3: ansi-escapes "^4.3.2" cross-spawn "^7.0.3" +path-browserify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd" + integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g== + path-case@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/path-case/-/path-case-3.0.4.tgz#9168645334eb942658375c56f80b4c0cb5f82c6f" @@ -7720,6 +7789,24 @@ pluralize@^8.0.0: resolved "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz" integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== +prebuild-install@^7.1.1: + version "7.1.2" + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.2.tgz#a5fd9986f5a6251fbc47e1e5c65de71e68c0a056" + integrity sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ== + dependencies: + detect-libc "^2.0.0" + expand-template "^2.0.3" + github-from-package "0.0.0" + minimist "^1.2.3" + mkdirp-classic "^0.5.3" + napi-build-utils "^1.0.1" + node-abi "^3.3.0" + pump "^3.0.0" + rc "^1.2.7" + simple-get "^4.0.0" + tar-fs "^2.0.0" + tunnel-agent "^0.6.0" + preferred-pm@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/preferred-pm/-/preferred-pm-3.0.3.tgz#1b6338000371e3edbce52ef2e4f65eb2e73586d6" @@ -7882,6 +7969,16 @@ raw-body@2.5.2: iconv-lite "0.4.24" unpipe "1.0.0" +rc@^1.2.7: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + read-cmd-shim@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/read-cmd-shim/-/read-cmd-shim-3.0.0.tgz#62b8c638225c61e6cc607f8f4b779f3b8238f155" @@ -7927,6 +8024,15 @@ readable-stream@^2.0.2, readable-stream@^2.3.5: string_decoder "~1.1.1" util-deprecate "~1.0.1" +readable-stream@^3.1.1: + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + readable-stream@^3.4.0, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz" @@ -7953,6 +8059,18 @@ readdirp@~3.6.0: dependencies: picomatch "^2.2.1" +realm@^12.6.2: + version "12.6.2" + resolved "https://registry.yarnpkg.com/realm/-/realm-12.6.2.tgz#01736e056b9346c3ac928bda1422e25360c94526" + integrity sha512-6ICUaKHNeiEAwVIKC3AkCDTCVEtpkFAVeWvmUVdmVIUjcY/+2cMLe/tgFpLcY7pEB/n1EUg3pVyUBcVuMvwdqg== + dependencies: + "@realm/fetch" "^0.1.1" + bson "^4.7.2" + debug "^4.3.4" + node-machine-id "^1.1.12" + path-browserify "^1.0.1" + prebuild-install "^7.1.1" + rechoir@^0.6.2: version "0.6.2" resolved "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz" @@ -8313,6 +8431,20 @@ signal-exit@^4.1.0: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== +simple-concat@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" + integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== + +simple-get@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543" + integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA== + dependencies: + decompress-response "^6.0.0" + once "^1.3.1" + simple-concat "^1.0.0" + simple-swizzle@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" @@ -8688,6 +8820,11 @@ strip-json-comments@3.1.1, strip-json-comments@^3.1.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== + strnum@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/strnum/-/strnum-1.0.5.tgz#5c4e829fe15ad4ff0d20c3db5ac97b73c9b072db" @@ -8748,6 +8885,27 @@ tapable@^2.2.0: resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== +tar-fs@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" + integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== + dependencies: + chownr "^1.1.1" + mkdirp-classic "^0.5.2" + pump "^3.0.0" + tar-stream "^2.1.4" + +tar-stream@^2.1.4: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" + integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== + dependencies: + bl "^4.0.3" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + tar@^6.0.2, tar@^6.1.0, tar@^6.1.11, tar@^6.1.2: version "6.1.11" resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" From 1227fcb843f5907ac17ff69070c82923e7c39b3c Mon Sep 17 00:00:00 2001 From: Pixo Date: Fri, 8 Mar 2024 14:49:21 +0100 Subject: [PATCH 02/13] [PB-1338]:(feature) Add Drive files realm tests --- package.json | 2 +- .../services/realms/drive-files.realm.test.ts | 40 +++++++++++++++++++ .../realms/drive-folders.realm.test.ts | 1 + 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 test/services/realms/drive-files.realm.test.ts create mode 100644 test/services/realms/drive-folders.realm.test.ts diff --git a/package.json b/package.json index 53c2ea9..2ca4c42 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "posttest": "yarn lint", "prepack": "yarn build && oclif manifest && oclif readme", "prepare": "husky", - "test:unit": "nyc --reporter=lcov --reporter=text mocha \"test/**/*.test.ts\"", + "test:unit": "nyc --reporter=lcov --reporter=text mocha \"test/**/*.test.ts\" --exit", "dev:webdav": "nodemon -e ts --exec 'ts-node src/webdav/index.ts'", "version": "oclif readme && git add README.md" }, diff --git a/test/services/realms/drive-files.realm.test.ts b/test/services/realms/drive-files.realm.test.ts new file mode 100644 index 0000000..2066981 --- /dev/null +++ b/test/services/realms/drive-files.realm.test.ts @@ -0,0 +1,40 @@ +import sinon, { SinonStubbedInstance } from 'sinon'; +import { DriveFileRealmSchema, DriveFilesRealm } from '../../../src/services/realms/drive-files.realm'; +import { Realm } from 'realm'; +import { expect } from 'chai'; +describe('Drive files realm', () => { + const sandbox = sinon.createSandbox(); + + afterEach(() => { + sandbox.restore(); + }); + it('When getByRelativePath is called, should return the correct object', async () => { + const realmMock = sandbox.createStubInstance(Realm); + const driveFilesRealm = new DriveFilesRealm(realmMock); + const relativePath = 'existing/path'; + // @ts-expect-error - Partial mock + const mockFile: DriveFileRealmSchema = { + id: 1, + name: 'Test File', + type: 'text', + uuid: 'file-uuid', + fileId: 'file-id', + folder_id: 1, + folder_uuid: 'folder-uuid', + bucket: 'test-bucket', + relative_path: relativePath, + created_at: new Date(), + updated_at: new Date(), + size: 1024, + status: 'EXISTS', + }; + + // @ts-expect-error - Partial mock + realmMock.objects.withArgs('DriveFile').returns({ filtered: sinon.stub().returns([mockFile]) }); + + const result = await driveFilesRealm.getByRelativePath(relativePath); + + expect(result).to.deep.equal(mockFile); + realmMock.close(); + }); +}); diff --git a/test/services/realms/drive-folders.realm.test.ts b/test/services/realms/drive-folders.realm.test.ts new file mode 100644 index 0000000..e3800a1 --- /dev/null +++ b/test/services/realms/drive-folders.realm.test.ts @@ -0,0 +1 @@ +describe('Drive folders realm', () => {}); From 788edc49d3a962602d1d5f968d8fb75f1620087c Mon Sep 17 00:00:00 2001 From: Pixo Date: Mon, 11 Mar 2024 12:12:28 +0100 Subject: [PATCH 03/13] [PB-1338]:(feature) Add Drive folders realm tests --- src/services/realms/drive-folders.realm.ts | 1 + .../services/realms/drive-files.realm.test.ts | 2 +- .../realms/drive-folders.realm.test.ts | 62 +++++++++++++++++++ 3 files changed, 64 insertions(+), 1 deletion(-) diff --git a/src/services/realms/drive-folders.realm.ts b/src/services/realms/drive-folders.realm.ts index 99a62aa..3f9ab99 100644 --- a/src/services/realms/drive-folders.realm.ts +++ b/src/services/realms/drive-folders.realm.ts @@ -52,6 +52,7 @@ export class DriveFoldersRealm { if (exists) { this.realm.delete(exists); } + this.realm.create('DriveFolder', { id: driveFolder.id, name: driveFolder.name, diff --git a/test/services/realms/drive-files.realm.test.ts b/test/services/realms/drive-files.realm.test.ts index 2066981..9282f5c 100644 --- a/test/services/realms/drive-files.realm.test.ts +++ b/test/services/realms/drive-files.realm.test.ts @@ -1,4 +1,4 @@ -import sinon, { SinonStubbedInstance } from 'sinon'; +import sinon from 'sinon'; import { DriveFileRealmSchema, DriveFilesRealm } from '../../../src/services/realms/drive-files.realm'; import { Realm } from 'realm'; import { expect } from 'chai'; diff --git a/test/services/realms/drive-folders.realm.test.ts b/test/services/realms/drive-folders.realm.test.ts index e3800a1..a2987d1 100644 --- a/test/services/realms/drive-folders.realm.test.ts +++ b/test/services/realms/drive-folders.realm.test.ts @@ -1 +1,63 @@ describe('Drive folders realm', () => {}); + +import sinon from 'sinon'; +import { DriveFolderRealmSchema, DriveFoldersRealm } from '../../../src/services/realms/drive-folders.realm'; +import { Realm } from 'realm'; +import { expect } from 'chai'; +import { DriveFolderItem } from '../../../src/types/drive.types'; +describe('Drive folders realm', () => { + const sandbox = sinon.createSandbox(); + + afterEach(() => { + sandbox.restore(); + }); + it('When findByRelativePath is called, should return the correct object', async () => { + const realmMock = sandbox.createStubInstance(Realm); + const driveFolderRealm = new DriveFoldersRealm(realmMock); + const relativePath = 'folder1/folder_a/'; + + // @ts-expect-error - Partial mock + const mockFolder: DriveFolderRealmSchema = { + id: 1, + name: 'folder_a', + uuid: 'uuid_1', + relative_path: 'folder1/folder_a/', + created_at: new Date(), + updated_at: new Date(), + status: 'EXISTS', + }; + + // @ts-expect-error - Partial mock + realmMock.objects.withArgs('DriveFolder').returns({ filtered: sinon.stub().returns([mockFolder]) }); + + const result = await driveFolderRealm.findByRelativePath(relativePath); + + expect(result).to.deep.equal(mockFolder); + realmMock.close(); + }); + + it('When create is called, should create the correct object', async () => { + const realmMock = sandbox.createStubInstance(Realm); + const driveFolderRealm = new DriveFoldersRealm(realmMock); + const relativePath = '/folder1/file.png'; + + const driveFolder: DriveFolderItem = { + id: 1, + name: 'file', + uuid: 'uuid_1', + parentId: 1, + createdAt: new Date(), + updatedAt: new Date(), + bucket: 'test-bucket', + encryptedName: 'encrypted-name', + }; + + realmMock.objectForPrimaryKey.withArgs('DriveFolder', driveFolder.id).returns(null); + + await driveFolderRealm.create(driveFolder, relativePath); + + expect(realmMock.objectForPrimaryKey.calledWith('DriveFolder', driveFolder.id)).to.be.true; + + realmMock.close(); + }); +}); From c5c340056c83db0ea925051d5aa7f57ffebe94fd Mon Sep 17 00:00:00 2001 From: Pixo Date: Mon, 11 Mar 2024 12:13:53 +0100 Subject: [PATCH 04/13] [PB-1338]:(feature) Add Drive folders realm tests --- src/services/realms/drive-realm-manager.service.ts | 2 +- src/utils/xml.utils.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/services/realms/drive-realm-manager.service.ts b/src/services/realms/drive-realm-manager.service.ts index e925d27..50b7cdb 100644 --- a/src/services/realms/drive-realm-manager.service.ts +++ b/src/services/realms/drive-realm-manager.service.ts @@ -1,5 +1,5 @@ import path from 'path'; -import { DriveFileItem, DriveFolderItem } from '../../types/drive.types'; +import { DriveFolderItem } from '../../types/drive.types'; import { DriveFilesRealm } from './drive-files.realm'; import { DriveFoldersRealm } from './drive-folders.realm'; diff --git a/src/utils/xml.utils.ts b/src/utils/xml.utils.ts index 2a4fcf2..4c647b2 100644 --- a/src/utils/xml.utils.ts +++ b/src/utils/xml.utils.ts @@ -6,12 +6,12 @@ export class XMLUtils { return parser.parse(xml); } - static toXML(object: Record, options: XmlBuilderOptions = { format: true }) { + static toXML(object: object, options: XmlBuilderOptions = { format: true }) { const builder = new XMLBuilder(options); return builder.build(object); } - static toWebDavXML(object: Record, options: XmlBuilderOptions) { + static toWebDavXML(object: object, options: XmlBuilderOptions) { const xmlContent = this.toXML(object, options); return `${xmlContent}`; } From c9eb9d951c4645beccc78daaec254290b65a50c0 Mon Sep 17 00:00:00 2001 From: Pixo Date: Mon, 11 Mar 2024 12:18:46 +0100 Subject: [PATCH 05/13] [PB-1338]:(feature) Exclude entry point for webdav server --- sonar-project.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sonar-project.properties b/sonar-project.properties index bc6ca55..e9c47c6 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -2,4 +2,4 @@ sonar.projectKey=internxt_cli sonar.organization=internxt sonar.javascript.lcov.reportPaths=./coverage/lcov.info sonar.sources=src/utils,src/services,src/webdav - +sonar.exclusions=src/webdav/index.ts \ No newline at end of file From 8f68c9fcac86f03e2de39e098241902c1c6e383a Mon Sep 17 00:00:00 2001 From: Pixo Date: Mon, 11 Mar 2024 12:47:40 +0100 Subject: [PATCH 06/13] [PB-1338]:(feature) Add XML utils tests --- src/services/realms/drive-files.realm.ts | 2 +- src/services/realms/drive-folders.realm.ts | 10 ++--- .../realms/drive-realm-manager.service.ts | 2 +- test/fixtures/drive-realm.fixture.ts | 2 +- .../realms/drive-folders.realm.test.ts | 40 ++++++++++++++----- test/utils/xml.utils.test.ts | 6 +++ 6 files changed, 44 insertions(+), 18 deletions(-) diff --git a/src/services/realms/drive-files.realm.ts b/src/services/realms/drive-files.realm.ts index 0d256da..3ac4485 100644 --- a/src/services/realms/drive-files.realm.ts +++ b/src/services/realms/drive-files.realm.ts @@ -14,7 +14,7 @@ export class DriveFileRealmSchema extends Realm.Object { updated_at!: Date; size!: number; status!: 'EXISTS' | 'REMOVED' | 'TRASHED'; - static schema: ObjectSchema = { + static readonly schema: ObjectSchema = { name: 'DriveFile', properties: { id: 'int', diff --git a/src/services/realms/drive-folders.realm.ts b/src/services/realms/drive-folders.realm.ts index 3f9ab99..e559bcd 100644 --- a/src/services/realms/drive-folders.realm.ts +++ b/src/services/realms/drive-folders.realm.ts @@ -10,7 +10,7 @@ export class DriveFolderRealmSchema extends Realm.Object created_at!: Date; updated_at!: Date; status!: 'EXISTS' | 'REMOVED' | 'TRASHED'; - static schema: ObjectSchema = { + static readonly schema: ObjectSchema = { name: 'DriveFolder', properties: { id: 'int', @@ -45,12 +45,12 @@ export class DriveFoldersRealm { return parentFolder; } - async create(driveFolder: DriveFolderItem, relativePath: string) { - const exists = this.realm.objectForPrimaryKey('DriveFolder', driveFolder.id); + async createOrReplace(driveFolder: DriveFolderItem, relativePath: string) { + const existingObject = this.realm.objectForPrimaryKey('DriveFolder', driveFolder.id); this.realm.write(() => { - if (exists) { - this.realm.delete(exists); + if (existingObject) { + this.realm.delete(existingObject); } this.realm.create('DriveFolder', { diff --git a/src/services/realms/drive-realm-manager.service.ts b/src/services/realms/drive-realm-manager.service.ts index 50b7cdb..59705ee 100644 --- a/src/services/realms/drive-realm-manager.service.ts +++ b/src/services/realms/drive-realm-manager.service.ts @@ -24,7 +24,7 @@ export class DriveRealmManager { async createFolder(driveFolder: DriveFolderItem) { const relativePath = await this.buildRelativePathForFolder(driveFolder.name, driveFolder.parentId ?? null); - return this.driveFoldersRealm.create(driveFolder, relativePath); + return this.driveFoldersRealm.createOrReplace(driveFolder, relativePath); } async buildRelativePathForFolder(folderName: string, parentId: number | null): Promise { diff --git a/test/fixtures/drive-realm.fixture.ts b/test/fixtures/drive-realm.fixture.ts index 79a0c0c..ee02963 100644 --- a/test/fixtures/drive-realm.fixture.ts +++ b/test/fixtures/drive-realm.fixture.ts @@ -58,7 +58,7 @@ export const getDriveRealmManager = (): DriveRealmManager => { const driveFoldersRealm: DriveFoldersRealm = { findByRelativePath: sinon.stub(), findByParentId: async () => null, - create: async () => { + createOrReplace: async () => { return; }, }; diff --git a/test/services/realms/drive-folders.realm.test.ts b/test/services/realms/drive-folders.realm.test.ts index a2987d1..58da5e7 100644 --- a/test/services/realms/drive-folders.realm.test.ts +++ b/test/services/realms/drive-folders.realm.test.ts @@ -5,6 +5,7 @@ import { DriveFolderRealmSchema, DriveFoldersRealm } from '../../../src/services import { Realm } from 'realm'; import { expect } from 'chai'; import { DriveFolderItem } from '../../../src/types/drive.types'; +import { getDriveFolderRealmSchemaFixture } from '../../fixtures/drive-realm.fixture'; describe('Drive folders realm', () => { const sandbox = sinon.createSandbox(); @@ -12,8 +13,8 @@ describe('Drive folders realm', () => { sandbox.restore(); }); it('When findByRelativePath is called, should return the correct object', async () => { - const realmMock = sandbox.createStubInstance(Realm); - const driveFolderRealm = new DriveFoldersRealm(realmMock); + const realmStub = sandbox.createStubInstance(Realm); + const driveFolderRealm = new DriveFoldersRealm(realmStub); const relativePath = 'folder1/folder_a/'; // @ts-expect-error - Partial mock @@ -28,17 +29,17 @@ describe('Drive folders realm', () => { }; // @ts-expect-error - Partial mock - realmMock.objects.withArgs('DriveFolder').returns({ filtered: sinon.stub().returns([mockFolder]) }); + realmStub.objects.withArgs('DriveFolder').returns({ filtered: sinon.stub().returns([mockFolder]) }); const result = await driveFolderRealm.findByRelativePath(relativePath); expect(result).to.deep.equal(mockFolder); - realmMock.close(); + realmStub.close(); }); it('When create is called, should create the correct object', async () => { - const realmMock = sandbox.createStubInstance(Realm); - const driveFolderRealm = new DriveFoldersRealm(realmMock); + const realmStub = sandbox.createStubInstance(Realm); + const driveFolderRealm = new DriveFoldersRealm(realmStub); const relativePath = '/folder1/file.png'; const driveFolder: DriveFolderItem = { @@ -52,12 +53,31 @@ describe('Drive folders realm', () => { encryptedName: 'encrypted-name', }; - realmMock.objectForPrimaryKey.withArgs('DriveFolder', driveFolder.id).returns(null); + realmStub.objectForPrimaryKey.withArgs('DriveFolder', driveFolder.id).returns(null); - await driveFolderRealm.create(driveFolder, relativePath); + await driveFolderRealm.createOrReplace(driveFolder, relativePath); - expect(realmMock.objectForPrimaryKey.calledWith('DriveFolder', driveFolder.id)).to.be.true; + expect(realmStub.objectForPrimaryKey.calledWith('DriveFolder', driveFolder.id)).to.be.true; - realmMock.close(); + realmStub.close(); + }); + + it('When findByParentId is called, should return the object with the same parentid', async () => { + const realmStub = sandbox.createStubInstance(Realm); + + const parentId = 1; + const parentFolder: DriveFolderRealmSchema = getDriveFolderRealmSchemaFixture({ + parent_id: parentId, + }); + + const driveFolderRealm = new DriveFoldersRealm(realmStub); + + realmStub.objectForPrimaryKey.withArgs('DriveFolder', parentId ?? -1).returns(parentFolder); + + const result = await driveFolderRealm.findByParentId(parentId); + + expect(realmStub.objectForPrimaryKey.calledWith('DriveFolder', parentId ?? -1)).to.be.true; + + expect(result).to.equal(parentFolder); }); }); diff --git a/test/utils/xml.utils.test.ts b/test/utils/xml.utils.test.ts index f016533..d3c2fbd 100644 --- a/test/utils/xml.utils.test.ts +++ b/test/utils/xml.utils.test.ts @@ -12,4 +12,10 @@ describe('XML utils', () => { const result = XMLUtils.toXML(object, { format: false }); expect(result).to.be.eq('value'); }); + + it('When providing an object, it should return a formatted XML', () => { + const object = { root: { child: 'value' } }; + const result = XMLUtils.toXML(object); + expect(result.replace(/\s/g, '')).to.be.eq('value'); + }); }); From da96fb83e520d728740034c7e91bd18e024f1989 Mon Sep 17 00:00:00 2001 From: Pixo Date: Mon, 11 Mar 2024 12:50:51 +0100 Subject: [PATCH 07/13] [PB-1338]:(feature) Add PROPFIND tests --- test/webdav/handlers/PROPFIND.handler.test.ts | 39 ++++++++++++++++--- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/test/webdav/handlers/PROPFIND.handler.test.ts b/test/webdav/handlers/PROPFIND.handler.test.ts index f84ce71..5d6615b 100644 --- a/test/webdav/handlers/PROPFIND.handler.test.ts +++ b/test/webdav/handlers/PROPFIND.handler.test.ts @@ -7,12 +7,7 @@ import { UserSettingsFixture } from '../../fixtures/auth.fixture'; import { newFolder, newPaginatedFolder } from '../../fixtures/drive.fixture'; import { createWebDavRequestFixture, createWebDavResponseFixture } from '../../fixtures/webdav.fixture'; import path from 'path'; -import { - getDriveFileRealmSchemaFixture, - getDriveFolderRealmSchemaFixture, - getDriveRealmManager, -} from '../../fixtures/drive-realm.fixture'; -import { get } from 'http'; +import { getDriveFolderRealmSchemaFixture, getDriveRealmManager } from '../../fixtures/drive-realm.fixture'; describe('PROPFIND request handler', () => { const sandbox = sinon.createSandbox(); @@ -176,4 +171,36 @@ describe('PROPFIND request handler', () => { sinon.assert.calledWith(response.status, 200); // TODO: Test the XML response }); + + it('When a WebDav client sends a PROPFIND request for a folder and it does not exists, should return a 404', async () => { + const configService = ConfigService.instance; + const driveFolderService = DriveFolderService.instance; + + sandbox + .stub(configService, 'readUser') + .resolves({ user: UserSettingsFixture, token: 'TOKEN', newToken: 'NEW_TOKEN', mnemonic: 'MNEMONIC' }); + + const driveRealmManager = getDriveRealmManager(); + sandbox.stub(driveRealmManager, 'findByRelativePath').resolves(null); + const requestHandler = new PROPFINDRequestHandler( + { debug: true }, + { + driveFolderService, + driveRealmManager, + }, + ); + + const request = createWebDavRequestFixture({ + url: '/folder_a', + method: 'PROPFIND', + }); + + const sendStub = sandbox.stub(); + const response = createWebDavResponseFixture({ + status: sandbox.stub().returns({ send: sendStub }), + }); + + await requestHandler.handle(request, response); + sinon.assert.calledWith(response.status, 404); + }); }); From e68d341d822559943ed3f4fde3c42e1e5dcb3a6c Mon Sep 17 00:00:00 2001 From: Pixo Date: Mon, 11 Mar 2024 13:00:07 +0100 Subject: [PATCH 08/13] [PB-1338]:(feature) Add PROPFIND tests --- src/webdav/handlers/PROPFIND.handler.ts | 21 ++++++------------- test/webdav/handlers/PROPFIND.handler.test.ts | 8 +++++-- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/src/webdav/handlers/PROPFIND.handler.ts b/src/webdav/handlers/PROPFIND.handler.ts index cb4b283..c2a287e 100644 --- a/src/webdav/handlers/PROPFIND.handler.ts +++ b/src/webdav/handlers/PROPFIND.handler.ts @@ -23,11 +23,6 @@ export class PROPFINDRequestHandler implements WebDavMethodHandler { const resource = this.getRequestedResource(req); switch (resource.type) { - case 'root': { - const rootFolder = await this.dependencies.driveFolderService.getFolderMetaById(req.user.rootFolderId); - res.status(200).send(await this.getFolderContentXML('/', rootFolder.uuid)); - break; - } case 'file': { const folderPath = path.join(path.dirname(resource.url), '/'); const driveParentFolder = await this.dependencies.driveRealmManager.findByRelativePath( @@ -44,6 +39,12 @@ export class PROPFINDRequestHandler implements WebDavMethodHandler { } case 'folder': { + if (resource.url === '/') { + const rootFolder = await this.dependencies.driveFolderService.getFolderMetaById(req.user.rootFolderId); + res.status(200).send(await this.getFolderContentXML('/', rootFolder.uuid)); + break; + } + const driveParentFolder = await this.dependencies.driveRealmManager.findByRelativePath( decodeURIComponent(resource.url), ); @@ -62,16 +63,6 @@ export class PROPFINDRequestHandler implements WebDavMethodHandler { private getRequestedResource(req: Request): WebDavRequestedResource { const parsedPath = path.parse(req.url); - // This is the root of the WebDav folder - if (req.url === '/') { - return { - type: 'root', - name: 'root', - url: req.url, - path: parsedPath, - }; - } - if (req.url.endsWith('/')) { return { url: req.url, diff --git a/test/webdav/handlers/PROPFIND.handler.test.ts b/test/webdav/handlers/PROPFIND.handler.test.ts index 5d6615b..2b2eedb 100644 --- a/test/webdav/handlers/PROPFIND.handler.test.ts +++ b/test/webdav/handlers/PROPFIND.handler.test.ts @@ -148,6 +148,10 @@ describe('PROPFIND request handler', () => { .resolves({ user: UserSettingsFixture, token: 'TOKEN', newToken: 'NEW_TOKEN', mnemonic: 'MNEMONIC' }); const driveRealmManager = getDriveRealmManager(); + sandbox.stub(driveFolderService, 'getFolderContent').resolves({ + files: [], + folders: [newPaginatedFolder()], + }); sandbox.stub(driveRealmManager, 'findByRelativePath').resolves(getDriveFolderRealmSchemaFixture()); const requestHandler = new PROPFINDRequestHandler( { debug: true }, @@ -158,7 +162,7 @@ describe('PROPFIND request handler', () => { ); const request = createWebDavRequestFixture({ - url: '/folder_a', + url: '/folder_a/', method: 'PROPFIND', }); @@ -191,7 +195,7 @@ describe('PROPFIND request handler', () => { ); const request = createWebDavRequestFixture({ - url: '/folder_a', + url: '/folder_a/', method: 'PROPFIND', }); From 19e8d6320f279a5b404faca1d0a2bbd8dd9fd56a Mon Sep 17 00:00:00 2001 From: Pixo Date: Mon, 11 Mar 2024 13:14:38 +0100 Subject: [PATCH 09/13] [PB-1338]:(feature) Add DriveRealm Manager tests --- src/services/realms/drive-files.realm.ts | 2 +- .../realms/drive-realm-manager.service.ts | 10 ++-- test/fixtures/drive-realm.fixture.ts | 4 +- .../services/realms/drive-files.realm.test.ts | 2 +- .../drive-realm-manager.service.test.ts | 58 +++++++++++++++++-- 5 files changed, 62 insertions(+), 14 deletions(-) diff --git a/src/services/realms/drive-files.realm.ts b/src/services/realms/drive-files.realm.ts index 3ac4485..392caa8 100644 --- a/src/services/realms/drive-files.realm.ts +++ b/src/services/realms/drive-files.realm.ts @@ -38,7 +38,7 @@ export class DriveFileRealmSchema extends Realm.Object { export class DriveFilesRealm { constructor(private realm: Realm) {} - async getByRelativePath(relativePath: string): Promise { + async findByRelativePath(relativePath: string): Promise { const object = this.realm .objects('DriveFile') .filtered('relative_path = $0', relativePath) diff --git a/src/services/realms/drive-realm-manager.service.ts b/src/services/realms/drive-realm-manager.service.ts index 59705ee..d22da3d 100644 --- a/src/services/realms/drive-realm-manager.service.ts +++ b/src/services/realms/drive-realm-manager.service.ts @@ -1,7 +1,7 @@ import path from 'path'; import { DriveFolderItem } from '../../types/drive.types'; -import { DriveFilesRealm } from './drive-files.realm'; -import { DriveFoldersRealm } from './drive-folders.realm'; +import { DriveFileRealmSchema, DriveFilesRealm } from './drive-files.realm'; +import { DriveFolderRealmSchema, DriveFoldersRealm } from './drive-folders.realm'; export class DriveRealmManager { constructor( @@ -9,8 +9,8 @@ export class DriveRealmManager { private driveFoldersRealm: DriveFoldersRealm, ) {} - async findByRelativePath(relativePath: string) { - const driveFile = await this.driveFilesRealm.getByRelativePath(relativePath); + async findByRelativePath(relativePath: string): Promise { + const driveFile = await this.driveFilesRealm.findByRelativePath(relativePath); if (driveFile) return driveFile; @@ -38,6 +38,4 @@ export class DriveRealmManager { return path.join(parentPath, folderName, '/'); } - - async close() {} } diff --git a/test/fixtures/drive-realm.fixture.ts b/test/fixtures/drive-realm.fixture.ts index ee02963..b222bdf 100644 --- a/test/fixtures/drive-realm.fixture.ts +++ b/test/fixtures/drive-realm.fixture.ts @@ -3,7 +3,7 @@ import { DriveFileRealmSchema, DriveFilesRealm } from '../../src/services/realms import { DriveFolderRealmSchema, DriveFoldersRealm } from '../../src/services/realms/drive-folders.realm'; import { DriveRealmManager } from '../../src/services/realms/drive-realm-manager.service'; -export const getDriveFileRealmSchemaFixture = (payload: Partial = {}): DriveFolderRealmSchema => { +export const getDriveFileRealmSchemaFixture = (payload: Partial = {}): DriveFileRealmSchema => { // @ts-expect-error - We only mock the properties we need const object: DriveFileRealmSchema = { id: new Date().getTime(), @@ -51,7 +51,7 @@ export const getDriveFolderRealmSchemaFixture = ( export const getDriveRealmManager = (): DriveRealmManager => { // @ts-expect-error - We only mock the properties we need const driveFilesRealm: DriveFilesRealm = { - getByRelativePath: sinon.stub(), + findByRelativePath: sinon.stub(), }; // @ts-expect-error - We only mock the properties we need diff --git a/test/services/realms/drive-files.realm.test.ts b/test/services/realms/drive-files.realm.test.ts index 9282f5c..6c3a281 100644 --- a/test/services/realms/drive-files.realm.test.ts +++ b/test/services/realms/drive-files.realm.test.ts @@ -32,7 +32,7 @@ describe('Drive files realm', () => { // @ts-expect-error - Partial mock realmMock.objects.withArgs('DriveFile').returns({ filtered: sinon.stub().returns([mockFile]) }); - const result = await driveFilesRealm.getByRelativePath(relativePath); + const result = await driveFilesRealm.findByRelativePath(relativePath); expect(result).to.deep.equal(mockFile); realmMock.close(); diff --git a/test/services/realms/drive-realm-manager.service.test.ts b/test/services/realms/drive-realm-manager.service.test.ts index 99c7ffe..ae4bba6 100644 --- a/test/services/realms/drive-realm-manager.service.test.ts +++ b/test/services/realms/drive-realm-manager.service.test.ts @@ -2,17 +2,67 @@ import sinon from 'sinon'; import { DriveFilesRealm } from '../../../src/services/realms/drive-files.realm'; import { DriveFoldersRealm } from '../../../src/services/realms/drive-folders.realm'; import { DriveRealmManager } from '../../../src/services/realms/drive-realm-manager.service'; -import { getDriveFolderRealmSchemaFixture } from '../../fixtures/drive-realm.fixture'; +import { getDriveFileRealmSchemaFixture, getDriveFolderRealmSchemaFixture } from '../../fixtures/drive-realm.fixture'; import { expect } from 'chai'; +import path from 'path'; describe('DriveRealmManager service', () => { const sandbox = sinon.createSandbox(); afterEach(() => { sandbox.restore(); }); + + it('When a relative path is provided for a file, should return the correct item', async () => { + // @ts-expect-error - We only mock the properties we need + const driveFilesRealm: DriveFilesRealm = { + findByRelativePath: async () => { + return null; + }, + }; + + // @ts-expect-error - We only mock the properties we need + const driveFoldersRealm: DriveFoldersRealm = { + findByRelativePath: sandbox.stub(), + findByParentId: async () => null, + }; + + const sut = new DriveRealmManager(driveFilesRealm, driveFoldersRealm); + + sandbox + .stub(driveFilesRealm, 'findByRelativePath') + .resolves(getDriveFileRealmSchemaFixture({ id: 1, name: 'file.png' })); + const item = await sut.findByRelativePath('/test/file.png'); + + expect(item?.id).to.be.equal(1); + }); + + it('When a relative path is provided for a folder, should return the correct item', async () => { + // @ts-expect-error - We only mock the properties we need + const driveFilesRealm: DriveFilesRealm = { + findByRelativePath: async () => { + return null; + }, + }; + + // @ts-expect-error - We only mock the properties we need + const driveFoldersRealm: DriveFoldersRealm = { + findByRelativePath: async () => null, + findByParentId: async () => null, + }; + + const sut = new DriveRealmManager(driveFilesRealm, driveFoldersRealm); + + sandbox.stub(driveFilesRealm, 'findByRelativePath').resolves(null); + sandbox + .stub(driveFoldersRealm, 'findByRelativePath') + .resolves(getDriveFolderRealmSchemaFixture({ id: 34, name: 'folder' })); + const item = await sut.findByRelativePath('/test/folder/'); + + expect(item?.id).to.be.equal(34); + }); it('When a folder is created, should build the correct relative path', async () => { // @ts-expect-error - We only mock the properties we need const driveFilesRealm: DriveFilesRealm = { - getByRelativePath: sandbox.stub(), + findByRelativePath: sandbox.stub(), }; // @ts-expect-error - We only mock the properties we need @@ -38,8 +88,8 @@ describe('DriveRealmManager service', () => { const sut = new DriveRealmManager(driveFilesRealm, driveFoldersRealm); - const path = await sut.buildRelativePathForFolder('folderD', 1); + const relativePath = await sut.buildRelativePathForFolder('folderD', 1); - expect(path).to.be.equal('/folderA/folderB/folderC/folderD/'); + expect(relativePath).to.be.equal(path.join('/', 'folderA', 'folderB', 'folderC', 'folderD', '/')); }); }); From 99c594c205b2fdac23fc2595b8322d2e85e8e6d3 Mon Sep 17 00:00:00 2001 From: Pixo Date: Mon, 11 Mar 2024 13:17:24 +0100 Subject: [PATCH 10/13] [PB-1338]:(feature) Fix code quality issues --- src/services/realms/drive-folders.realm.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/services/realms/drive-folders.realm.ts b/src/services/realms/drive-folders.realm.ts index e559bcd..a706900 100644 --- a/src/services/realms/drive-folders.realm.ts +++ b/src/services/realms/drive-folders.realm.ts @@ -64,7 +64,5 @@ export class DriveFoldersRealm { relative_path: relativePath, }); }); - - return; } } From 96f99460aa8eba0a1ff401570df3469709c11d77 Mon Sep 17 00:00:00 2001 From: Pixo Date: Mon, 11 Mar 2024 13:25:44 +0100 Subject: [PATCH 11/13] [PB-1338]:(feature) Add Internxt CLI data dir to config service --- src/services/config.service.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/services/config.service.ts b/src/services/config.service.ts index 1e50871..1ea3ebd 100644 --- a/src/services/config.service.ts +++ b/src/services/config.service.ts @@ -6,8 +6,9 @@ import { LoginCredentials } from '../types/command.types'; import { CryptoService } from './crypto.service'; export class ConfigService { - static readonly CREDENTIALS_FILE = path.join(os.homedir(), '.inxtcli'); - static readonly DRIVE_REALM_FILE = path.join(os.homedir(), 'internxt-cli-drive.realm'); + static readonly INTERNXT_CLI_DATA_DIR = path.join(os.homedir(), 'internxt-cli'); + static readonly CREDENTIALS_FILE = path.join(this.INTERNXT_CLI_DATA_DIR, '.inxtcli'); + static readonly DRIVE_REALM_FILE = path.join(this.INTERNXT_CLI_DATA_DIR, 'internxt-cli-drive.realm'); public static readonly instance: ConfigService = new ConfigService(); /** From 8149a1864a4324440cb27d8fa7bdba488a45d4bd Mon Sep 17 00:00:00 2001 From: Pixo Date: Mon, 11 Mar 2024 13:40:41 +0100 Subject: [PATCH 12/13] [PB-1338]:(feature) Hide internxt-cli data dir --- src/services/config.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/config.service.ts b/src/services/config.service.ts index 1ea3ebd..17b303e 100644 --- a/src/services/config.service.ts +++ b/src/services/config.service.ts @@ -6,7 +6,7 @@ import { LoginCredentials } from '../types/command.types'; import { CryptoService } from './crypto.service'; export class ConfigService { - static readonly INTERNXT_CLI_DATA_DIR = path.join(os.homedir(), 'internxt-cli'); + static readonly INTERNXT_CLI_DATA_DIR = path.join(os.homedir(), '.internxt-cli'); static readonly CREDENTIALS_FILE = path.join(this.INTERNXT_CLI_DATA_DIR, '.inxtcli'); static readonly DRIVE_REALM_FILE = path.join(this.INTERNXT_CLI_DATA_DIR, 'internxt-cli-drive.realm'); public static readonly instance: ConfigService = new ConfigService(); From 6148995fe4f9e524478d6857a835b7c88de72f9f Mon Sep 17 00:00:00 2001 From: Pixo Date: Mon, 11 Mar 2024 13:49:29 +0100 Subject: [PATCH 13/13] [PB-1338]:(feature) Ensure internxt-cli data dir exists --- src/services/config.service.ts | 9 +++++++++ src/webdav/index.ts | 1 + 2 files changed, 10 insertions(+) diff --git a/src/services/config.service.ts b/src/services/config.service.ts index 17b303e..f20cc95 100644 --- a/src/services/config.service.ts +++ b/src/services/config.service.ts @@ -29,6 +29,7 @@ export class ConfigService { * @async **/ public saveUser = async (loginCredentials: LoginCredentials): Promise => { + await this.ensureInternxtCliDataDirExists(); const credentialsString = JSON.stringify(loginCredentials); const encryptedCredentials = CryptoService.instance.encryptText(credentialsString); await fs.writeFile(ConfigService.CREDENTIALS_FILE, encryptedCredentials, 'utf8'); @@ -65,4 +66,12 @@ export class ConfigService { return; } }; + + ensureInternxtCliDataDirExists = async () => { + try { + await fs.access(ConfigService.INTERNXT_CLI_DATA_DIR); + } catch { + await fs.mkdir(ConfigService.INTERNXT_CLI_DATA_DIR); + } + }; } diff --git a/src/webdav/index.ts b/src/webdav/index.ts index 7231921..5b36ffb 100644 --- a/src/webdav/index.ts +++ b/src/webdav/index.ts @@ -11,6 +11,7 @@ import { DriveFoldersRealm, DriveFolderRealmSchema } from '../services/realms/dr dotenv.config(); const init = async () => { + await ConfigService.instance.ensureInternxtCliDataDirExists(); const realm = await Realm.open({ path: ConfigService.DRIVE_REALM_FILE, schema: [DriveFileRealmSchema, DriveFolderRealmSchema],