feat: implement path based operations
This commit is contained in:
11
jest.config.js
Normal file
11
jest.config.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
const { createDefaultPreset } = require("ts-jest");
|
||||||
|
|
||||||
|
const tsJestTransformCfg = createDefaultPreset().transform;
|
||||||
|
|
||||||
|
/** @type {import("jest").Config} **/
|
||||||
|
module.exports = {
|
||||||
|
testEnvironment: "node",
|
||||||
|
transform: {
|
||||||
|
...tsJestTransformCfg,
|
||||||
|
},
|
||||||
|
};
|
||||||
4159
package-lock.json
generated
4159
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
21
package.json
21
package.json
@@ -5,20 +5,27 @@
|
|||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc && cp src/nodes/KDrive/kdrive.svg dist/nodes/KDrive/kdrive.svg",
|
"build": "tsc && cp src/nodes/KDrive/kdrive.svg dist/nodes/KDrive/kdrive.svg",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "jest"
|
||||||
},
|
},
|
||||||
"keywords": ["n8n", "kdrive", "infomaniak"],
|
"keywords": [
|
||||||
|
"n8n",
|
||||||
|
"kdrive",
|
||||||
|
"infomaniak"
|
||||||
|
],
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "LGPL-3.0",
|
"license": "LGPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": "^20.0.0",
|
"@types/node": "^20.0.0",
|
||||||
|
"@types/request": "^2.48.12",
|
||||||
"n8n-workflow": "^1.0.0",
|
"n8n-workflow": "^1.0.0",
|
||||||
"request": "^2.88.2",
|
"request": "^2.88.2"
|
||||||
"@types/request": "^2.48.12"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^5.0.0",
|
"@types/jest": "^30.0.0",
|
||||||
"@types/node": "^20.0.0"
|
"@types/node": "^20.0.0",
|
||||||
|
"jest": "^30.2.0",
|
||||||
|
"ts-jest": "^29.4.6",
|
||||||
|
"typescript": "^5.0.0"
|
||||||
},
|
},
|
||||||
"n8n": {
|
"n8n": {
|
||||||
"nodes": [
|
"nodes": [
|
||||||
@@ -28,4 +35,4 @@
|
|||||||
"KDrive"
|
"KDrive"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,21 @@ import {
|
|||||||
|
|
||||||
import * as request from 'request';
|
import * as request from 'request';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for path resolution cache
|
||||||
|
*/
|
||||||
|
interface PathCacheEntry {
|
||||||
|
fileId: string;
|
||||||
|
fileName: string;
|
||||||
|
parentId: string;
|
||||||
|
timestamp: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Path cache to store resolved paths for performance
|
||||||
|
*/
|
||||||
|
const pathCache: Record<string, PathCacheEntry> = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make an API request to kDrive API
|
* Make an API request to kDrive API
|
||||||
*/
|
*/
|
||||||
@@ -99,4 +114,180 @@ export function handleApiError(this: IExecuteFunctions, error: any): void {
|
|||||||
});
|
});
|
||||||
|
|
||||||
throw new Error(`kDrive API Error: ${errorMessage}`);
|
throw new Error(`kDrive API Error: ${errorMessage}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear path cache
|
||||||
|
*/
|
||||||
|
export function clearPathCache(): void {
|
||||||
|
Object.keys(pathCache).forEach(key => delete pathCache[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize path - convert to absolute path and handle path separators
|
||||||
|
*/
|
||||||
|
export function normalizePath(path: string): string {
|
||||||
|
// Replace Windows-style backslashes with forward slashes
|
||||||
|
let normalized = path.replace(/\\/g, '/');
|
||||||
|
|
||||||
|
// Remove leading/trailing slashes
|
||||||
|
normalized = normalized.replace(/^\/+|\/+$/g, '');
|
||||||
|
|
||||||
|
// Handle empty path (root)
|
||||||
|
if (!normalized) {
|
||||||
|
return 'root';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collapse multiple consecutive slashes
|
||||||
|
normalized = normalized.replace(/\/+/g, '/');
|
||||||
|
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse path into components
|
||||||
|
*/
|
||||||
|
export function parsePath(path: string): string[] {
|
||||||
|
const normalized = normalizePath(path);
|
||||||
|
|
||||||
|
if (normalized === 'root') {
|
||||||
|
return ['root'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split by slash and filter out empty segments
|
||||||
|
return normalized.split('/').filter(component => component.length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get file info by ID
|
||||||
|
*/
|
||||||
|
export async function getFileInfoById(
|
||||||
|
this: IExecuteFunctions,
|
||||||
|
fileId: string,
|
||||||
|
driveId: string,
|
||||||
|
credentials: IDataObject
|
||||||
|
): Promise<any> {
|
||||||
|
if (fileId === 'root') {
|
||||||
|
return {
|
||||||
|
id: 'root',
|
||||||
|
name: 'root',
|
||||||
|
type: 'directory',
|
||||||
|
parent_id: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await kdriveApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
`/3/drive/${driveId}/files/${fileId}`,
|
||||||
|
{},
|
||||||
|
credentials
|
||||||
|
);
|
||||||
|
return response;
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to get file info for ID ${fileId}: ${error}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve path to file ID
|
||||||
|
*/
|
||||||
|
export async function resolvePathToId(
|
||||||
|
this: IExecuteFunctions,
|
||||||
|
path: string,
|
||||||
|
driveId: string,
|
||||||
|
credentials: IDataObject
|
||||||
|
): Promise<string> {
|
||||||
|
const normalizedPath = normalizePath(path);
|
||||||
|
|
||||||
|
// Check cache first
|
||||||
|
if (pathCache[normalizedPath]) {
|
||||||
|
return pathCache[normalizedPath].fileId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle root path
|
||||||
|
if (normalizedPath === 'root') {
|
||||||
|
return 'root';
|
||||||
|
}
|
||||||
|
|
||||||
|
const pathComponents = parsePath(normalizedPath);
|
||||||
|
let currentId = 'root';
|
||||||
|
|
||||||
|
// Traverse path component by component
|
||||||
|
for (const component of pathComponents) {
|
||||||
|
// Get contents of current directory
|
||||||
|
const endpoint = currentId === 'root'
|
||||||
|
? `/3/drive/${driveId}/files/1/files`
|
||||||
|
: `/3/drive/${driveId}/files/${currentId}/files`;
|
||||||
|
|
||||||
|
const response = await kdriveApiRequest.call(this, 'GET', endpoint, {}, credentials);
|
||||||
|
|
||||||
|
// Find the component in the current directory
|
||||||
|
const foundItem = response.items.find((item: any) =>
|
||||||
|
item.name === component && item.type === 'directory'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!foundItem) {
|
||||||
|
throw new Error(`Path component not found: ${component} in ${currentId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
currentId = foundItem.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache the resolved path
|
||||||
|
pathCache[normalizedPath] = {
|
||||||
|
fileId: currentId,
|
||||||
|
fileName: pathComponents[pathComponents.length - 1],
|
||||||
|
parentId: pathComponents.length === 1 ? 'root' :
|
||||||
|
pathCache[parsePath(normalizedPath).slice(0, -1).join('/')]?.fileId || 'root',
|
||||||
|
timestamp: Date.now()
|
||||||
|
};
|
||||||
|
|
||||||
|
return currentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve file ID to path
|
||||||
|
*/
|
||||||
|
export async function resolveIdToPath(
|
||||||
|
this: IExecuteFunctions,
|
||||||
|
fileId: string,
|
||||||
|
driveId: string,
|
||||||
|
credentials: IDataObject
|
||||||
|
): Promise<string> {
|
||||||
|
if (fileId === 'root') {
|
||||||
|
return 'root';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we have this in cache
|
||||||
|
const cachedEntry = Object.values(pathCache).find(entry => entry.fileId === fileId);
|
||||||
|
if (cachedEntry) {
|
||||||
|
return Object.keys(pathCache).find(key => pathCache[key].fileId === fileId) || fileId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get file info
|
||||||
|
const fileInfo = await getFileInfoById.call(this, fileId, driveId, credentials);
|
||||||
|
|
||||||
|
if (!fileInfo.parent_id) {
|
||||||
|
return fileInfo.name; // Root level file
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursively get parent path
|
||||||
|
const parentPath = await resolveIdToPath.call(this, fileInfo.parent_id, driveId, credentials);
|
||||||
|
|
||||||
|
// Construct full path
|
||||||
|
const fullPath = parentPath === 'root'
|
||||||
|
? fileInfo.name
|
||||||
|
: `${parentPath}/${fileInfo.name}`;
|
||||||
|
|
||||||
|
// Cache the result
|
||||||
|
pathCache[fullPath] = {
|
||||||
|
fileId: fileInfo.id,
|
||||||
|
fileName: fileInfo.name,
|
||||||
|
parentId: fileInfo.parent_id,
|
||||||
|
timestamp: Date.now()
|
||||||
|
};
|
||||||
|
|
||||||
|
return fullPath;
|
||||||
}
|
}
|
||||||
@@ -9,6 +9,9 @@ import {
|
|||||||
import {
|
import {
|
||||||
kdriveApiRequest,
|
kdriveApiRequest,
|
||||||
handleApiError,
|
handleApiError,
|
||||||
|
resolvePathToId,
|
||||||
|
parsePath,
|
||||||
|
normalizePath,
|
||||||
} from './GenericFunctions';
|
} from './GenericFunctions';
|
||||||
|
|
||||||
export class KDrive implements INodeType {
|
export class KDrive implements INodeType {
|
||||||
@@ -80,7 +83,7 @@ export class KDrive implements INodeType {
|
|||||||
resource: ['file'],
|
resource: ['file'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
name: 'List Files',
|
name: 'List Files',
|
||||||
value: 'listFiles',
|
value: 'listFiles',
|
||||||
@@ -91,21 +94,41 @@ export class KDrive implements INodeType {
|
|||||||
value: 'getFileInfo',
|
value: 'getFileInfo',
|
||||||
description: 'Get information about a file',
|
description: 'Get information about a file',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'Get File Info by Path',
|
||||||
|
value: 'getFileInfoByPath',
|
||||||
|
description: 'Get information about a file using path',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'Upload File',
|
name: 'Upload File',
|
||||||
value: 'uploadFile',
|
value: 'uploadFile',
|
||||||
description: 'Upload a file',
|
description: 'Upload a file',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'Upload File by Path',
|
||||||
|
value: 'uploadFileByPath',
|
||||||
|
description: 'Upload a file to specific path',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'Download File',
|
name: 'Download File',
|
||||||
value: 'downloadFile',
|
value: 'downloadFile',
|
||||||
description: 'Download a file',
|
description: 'Download a file',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'Download File by Path',
|
||||||
|
value: 'downloadFileByPath',
|
||||||
|
description: 'Download a file using path',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'Delete File',
|
name: 'Delete File',
|
||||||
value: 'deleteFile',
|
value: 'deleteFile',
|
||||||
description: 'Delete a file (move to trash)',
|
description: 'Delete a file (move to trash)',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'Delete File by Path',
|
||||||
|
value: 'deleteFileByPath',
|
||||||
|
description: 'Delete a file using path',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'Search Files',
|
name: 'Search Files',
|
||||||
value: 'searchFiles',
|
value: 'searchFiles',
|
||||||
@@ -116,6 +139,11 @@ export class KDrive implements INodeType {
|
|||||||
value: 'getFileVersions',
|
value: 'getFileVersions',
|
||||||
description: 'Get file versions',
|
description: 'Get file versions',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'Get File Versions by Path',
|
||||||
|
value: 'getFileVersionsByPath',
|
||||||
|
description: 'Get file versions using path',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
default: 'listFiles',
|
default: 'listFiles',
|
||||||
},
|
},
|
||||||
@@ -166,12 +194,52 @@ export class KDrive implements INodeType {
|
|||||||
default: 'root',
|
default: 'root',
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
resource: ['file'],
|
resource: ['directory'],
|
||||||
operation: ['listFiles'],
|
operation: ['createDirectory', 'createFile'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
description: 'The ID of the parent directory (use "root" for root directory)',
|
description: 'The ID of the parent directory (use "root" for root directory)',
|
||||||
},
|
},
|
||||||
|
// Path-based parameters for file operations
|
||||||
|
{
|
||||||
|
displayName: 'File Path',
|
||||||
|
name: 'filePath',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['file'],
|
||||||
|
operation: ['getFileInfoByPath', 'downloadFileByPath', 'deleteFileByPath', 'getFileVersionsByPath'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: 'The path to the file (e.g., "documents/report.pdf")',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Directory Path',
|
||||||
|
name: 'directoryPath',
|
||||||
|
type: 'string',
|
||||||
|
default: 'root',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['file'],
|
||||||
|
operation: ['listFiles'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: 'The path to the directory (use "root" for root, e.g., "documents/project")',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Upload Path',
|
||||||
|
name: 'uploadPath',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['file'],
|
||||||
|
operation: ['uploadFileByPath'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: 'The full path where to upload the file (e.g., "documents/report.pdf")',
|
||||||
|
},
|
||||||
// Directory ID for directory operations
|
// Directory ID for directory operations
|
||||||
{
|
{
|
||||||
displayName: 'Parent Directory ID',
|
displayName: 'Parent Directory ID',
|
||||||
@@ -322,9 +390,18 @@ export class KDrive implements INodeType {
|
|||||||
const driveId = credentials.driveId as string;
|
const driveId = credentials.driveId as string;
|
||||||
|
|
||||||
if (operation === 'listFiles') {
|
if (operation === 'listFiles') {
|
||||||
const parentDirectoryId = this.getNodeParameter('parentDirectoryId', i) as string;
|
let parentDirectoryId = this.getNodeParameter('parentDirectoryId', i) as string;
|
||||||
|
const directoryPath = this.getNodeParameter('directoryPath', i) as string;
|
||||||
|
|
||||||
|
// If directoryPath is provided, resolve it to an ID
|
||||||
|
if (directoryPath && directoryPath !== 'root') {
|
||||||
|
parentDirectoryId = await resolvePathToId.call(this, directoryPath, driveId, credentials);
|
||||||
|
} else if (directoryPath === 'root') {
|
||||||
|
parentDirectoryId = 'root';
|
||||||
|
}
|
||||||
|
|
||||||
const endpoint = parentDirectoryId === 'root'
|
const endpoint = parentDirectoryId === 'root'
|
||||||
? `/3/drive/${driveId}/files/root/files`
|
? `/3/drive/${driveId}/files/1/files`
|
||||||
: `/3/drive/${driveId}/files/${parentDirectoryId}/files`;
|
: `/3/drive/${driveId}/files/${parentDirectoryId}/files`;
|
||||||
const response = await kdriveApiRequest.call(this, 'GET', endpoint, {}, credentials);
|
const response = await kdriveApiRequest.call(this, 'GET', endpoint, {}, credentials);
|
||||||
returnData.push({ json: response });
|
returnData.push({ json: response });
|
||||||
@@ -364,10 +441,48 @@ export class KDrive implements INodeType {
|
|||||||
|
|
||||||
const response = await kdriveApiRequest.call(this, 'GET', `/3/drive/${driveId}/files/search`, params, credentials);
|
const response = await kdriveApiRequest.call(this, 'GET', `/3/drive/${driveId}/files/search`, params, credentials);
|
||||||
returnData.push({ json: response });
|
returnData.push({ json: response });
|
||||||
} else if (operation === 'getFileVersions') {
|
} else if (operation === 'getFileVersions') {
|
||||||
const fileId = this.getNodeParameter('fileId', i) as string;
|
const fileId = this.getNodeParameter('fileId', i) as string;
|
||||||
const response = await kdriveApiRequest.call(this, 'GET', `/3/drive/${driveId}/files/${fileId}/versions`, {}, credentials);
|
const response = await kdriveApiRequest.call(this, 'GET', `/3/drive/${driveId}/files/${fileId}/versions`, {}, credentials);
|
||||||
returnData.push({ json: response });
|
returnData.push({ json: response });
|
||||||
|
} else if (operation === 'getFileInfoByPath') {
|
||||||
|
const filePath = this.getNodeParameter('filePath', i) as string;
|
||||||
|
const fileId = await resolvePathToId.call(this, filePath, driveId, credentials);
|
||||||
|
const response = await kdriveApiRequest.call(this, 'GET', `/3/drive/${driveId}/files/${fileId}`, {}, credentials);
|
||||||
|
returnData.push({ json: response });
|
||||||
|
} else if (operation === 'uploadFileByPath') {
|
||||||
|
const fileData = this.getNodeParameter('fileData', i) as string;
|
||||||
|
const uploadPath = this.getNodeParameter('uploadPath', i) as string;
|
||||||
|
|
||||||
|
// Parse path to get directory and filename
|
||||||
|
const pathComponents = parsePath(uploadPath);
|
||||||
|
const fileName = pathComponents[pathComponents.length - 1];
|
||||||
|
const parentPath = pathComponents.slice(0, -1).join('/') || 'root';
|
||||||
|
const parentDirectoryId = await resolvePathToId.call(this, parentPath, driveId, credentials);
|
||||||
|
|
||||||
|
const formData: IDataObject = {
|
||||||
|
file: fileData,
|
||||||
|
filename: fileName,
|
||||||
|
parent_id: parentDirectoryId === 'root' ? 'root' : parentDirectoryId,
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await kdriveApiRequest.call(this, 'POST', `/3/drive/${driveId}/upload`, formData, credentials);
|
||||||
|
returnData.push({ json: response });
|
||||||
|
} else if (operation === 'downloadFileByPath') {
|
||||||
|
const filePath = this.getNodeParameter('filePath', i) as string;
|
||||||
|
const fileId = await resolvePathToId.call(this, filePath, driveId, credentials);
|
||||||
|
const response = await kdriveApiRequest.call(this, 'GET', `/2/drive/${driveId}/files/${fileId}/download`, {}, credentials, true);
|
||||||
|
returnData.push({ json: {}, binary: { data: { mimeType: 'application/octet-stream', data: response } } });
|
||||||
|
} else if (operation === 'deleteFileByPath') {
|
||||||
|
const filePath = this.getNodeParameter('filePath', i) as string;
|
||||||
|
const fileId = await resolvePathToId.call(this, filePath, driveId, credentials);
|
||||||
|
const response = await kdriveApiRequest.call(this, 'DELETE', `/2/drive/${driveId}/files/${fileId}`, {}, credentials);
|
||||||
|
returnData.push({ json: response });
|
||||||
|
} else if (operation === 'getFileVersionsByPath') {
|
||||||
|
const filePath = this.getNodeParameter('filePath', i) as string;
|
||||||
|
const fileId = await resolvePathToId.call(this, filePath, driveId, credentials);
|
||||||
|
const response = await kdriveApiRequest.call(this, 'GET', `/3/drive/${driveId}/files/${fileId}/versions`, {}, credentials);
|
||||||
|
returnData.push({ json: response });
|
||||||
}
|
}
|
||||||
} else if (resource === 'directory') {
|
} else if (resource === 'directory') {
|
||||||
const driveId = credentials.driveId as string;
|
const driveId = credentials.driveId as string;
|
||||||
|
|||||||
153
tests/KDrive.node.test.ts
Normal file
153
tests/KDrive.node.test.ts
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
import { KDrive } from '../src/nodes/KDrive/KDrive.node';
|
||||||
|
import {
|
||||||
|
kdriveApiRequest,
|
||||||
|
handleApiError,
|
||||||
|
resolvePathToId,
|
||||||
|
parsePath,
|
||||||
|
normalizePath,
|
||||||
|
resolveIdToPath,
|
||||||
|
clearPathCache
|
||||||
|
} from '../src/nodes/KDrive/GenericFunctions';
|
||||||
|
|
||||||
|
describe('KDrive Node', () => {
|
||||||
|
let kdriveNode: KDrive;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
kdriveNode = new KDrive();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Node Description', () => {
|
||||||
|
it('should have correct node description', () => {
|
||||||
|
expect(kdriveNode.description.displayName).toBe('kDrive');
|
||||||
|
expect(kdriveNode.description.name).toBe('kDrive');
|
||||||
|
expect(kdriveNode.description.group).toContain('transform');
|
||||||
|
expect(kdriveNode.description.version).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have required credentials', () => {
|
||||||
|
expect(kdriveNode.description.credentials).toBeDefined();
|
||||||
|
if (kdriveNode.description.credentials) {
|
||||||
|
expect(kdriveNode.description.credentials.length).toBe(1);
|
||||||
|
expect(kdriveNode.description.credentials[0].name).toBe('kDriveApi');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Node Properties', () => {
|
||||||
|
it('should have authentication property', () => {
|
||||||
|
const authProperty = kdriveNode.description.properties.find(p => p.name === 'authentication');
|
||||||
|
expect(authProperty).toBeDefined();
|
||||||
|
if (authProperty) {
|
||||||
|
expect(authProperty.type).toBe('options');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have resource property', () => {
|
||||||
|
const resourceProperty = kdriveNode.description.properties.find(p => p.name === 'resource');
|
||||||
|
expect(resourceProperty).toBeDefined();
|
||||||
|
if (resourceProperty) {
|
||||||
|
expect(resourceProperty.type).toBe('options');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have operation property', () => {
|
||||||
|
const operationProperty = kdriveNode.description.properties.find(p => p.name === 'operation');
|
||||||
|
expect(operationProperty).toBeDefined();
|
||||||
|
if (operationProperty) {
|
||||||
|
expect(operationProperty.type).toBe('options');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have path-based operation options', () => {
|
||||||
|
const operationProperty = kdriveNode.description.properties.find(p => p.name === 'operation') as any;
|
||||||
|
expect(operationProperty).toBeDefined();
|
||||||
|
if (operationProperty && operationProperty.options) {
|
||||||
|
const operationValues = operationProperty.options.map((opt: any) => opt.value);
|
||||||
|
expect(operationValues).toContain('getFileInfoByPath');
|
||||||
|
expect(operationValues).toContain('uploadFileByPath');
|
||||||
|
expect(operationValues).toContain('downloadFileByPath');
|
||||||
|
expect(operationValues).toContain('deleteFileByPath');
|
||||||
|
expect(operationValues).toContain('getFileVersionsByPath');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have path-based parameters', () => {
|
||||||
|
const filePathParam = kdriveNode.description.properties.find(p => p.name === 'filePath');
|
||||||
|
expect(filePathParam).toBeDefined();
|
||||||
|
|
||||||
|
const directoryPathParam = kdriveNode.description.properties.find(p => p.name === 'directoryPath');
|
||||||
|
expect(directoryPathParam).toBeDefined();
|
||||||
|
|
||||||
|
const uploadPathParam = kdriveNode.description.properties.find(p => p.name === 'uploadPath');
|
||||||
|
expect(uploadPathParam).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Generic Functions', () => {
|
||||||
|
describe('kdriveApiRequest', () => {
|
||||||
|
it('should be a function', () => {
|
||||||
|
expect(typeof kdriveApiRequest).toBe('function');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('handleApiError', () => {
|
||||||
|
it('should be a function', () => {
|
||||||
|
expect(typeof handleApiError).toBe('function');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Path Resolution Functions', () => {
|
||||||
|
describe('normalizePath', () => {
|
||||||
|
it('should normalize Windows paths', () => {
|
||||||
|
expect(normalizePath('documents\\file.txt')).toBe('documents/file.txt');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle leading/trailing slashes', () => {
|
||||||
|
expect(normalizePath('/documents/file.txt/')).toBe('documents/file.txt');
|
||||||
|
expect(normalizePath('///documents///file.txt///')).toBe('documents/file.txt');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle empty path as root', () => {
|
||||||
|
expect(normalizePath('')).toBe('root');
|
||||||
|
expect(normalizePath('/')).toBe('root');
|
||||||
|
expect(normalizePath('///')).toBe('root');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle root path', () => {
|
||||||
|
expect(normalizePath('root')).toBe('root');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('parsePath', () => {
|
||||||
|
it('should parse simple paths', () => {
|
||||||
|
expect(parsePath('file.txt')).toEqual(['file.txt']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse nested paths', () => {
|
||||||
|
expect(parsePath('documents/project/file.txt')).toEqual(['documents', 'project', 'file.txt']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle root path', () => {
|
||||||
|
expect(parsePath('root')).toEqual(['root']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should normalize before parsing', () => {
|
||||||
|
expect(parsePath('/documents//project///file.txt/')).toEqual(['documents', 'project', 'file.txt']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('clearPathCache', () => {
|
||||||
|
it('should be a function', () => {
|
||||||
|
expect(typeof clearPathCache).toBe('function');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('resolvePathToId and resolveIdToPath', () => {
|
||||||
|
it('should be functions', () => {
|
||||||
|
expect(typeof resolvePathToId).toBe('function');
|
||||||
|
expect(typeof resolveIdToPath).toBe('function');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user