Skip to content

Commit

Permalink
Improve Test (#11)
Browse files Browse the repository at this point in the history
* update dependencies

* add test for source function

* update dependencies

* add more adapter tests

* add test for common functions

* add test for uniq path segments
  • Loading branch information
CordlessWool authored Mar 26, 2024
1 parent 61b560f commit 1af2566
Show file tree
Hide file tree
Showing 18 changed files with 505 additions and 241 deletions.
31 changes: 31 additions & 0 deletions adapters/in-memory/src/exceptions.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { describe, test, expect } from 'vitest';
import { AlreadyExistsException, NotFoundException } from './exceptions';
import { MEMORY_TYPE } from './definitions';

describe('exceptions', () => {
test('NotFoundException with root', () => {
const error = new NotFoundException('test', { $type: MEMORY_TYPE.ROOT, content: [] });
expect(error.message).toBe('Could not find test');
expect(error.last).toEqual({ $type: MEMORY_TYPE.ROOT, content: [] });
expect(error.depth).toBe(0);
});

test('NotFoundException with directory', () => {
const error = new NotFoundException('test', { $type: MEMORY_TYPE.DIRECTORY, name:'test', content: [] });
expect(error.message).toBe('Could not find test');
expect(error.last).toEqual({ $type: MEMORY_TYPE.DIRECTORY, name: 'test', content: [] });
expect(error.depth).toBe(0);
});

test('AlreadyExistsException with root', () => {
const error = new AlreadyExistsException('test', { $type: MEMORY_TYPE.ROOT, content: [] });
expect(error.message).toBe('Already exists test');
expect(error.ref).toEqual({ $type: MEMORY_TYPE.ROOT, content: [] });
});

test('AlreadyExistsException with directory', () => {
const error = new AlreadyExistsException('test', { $type: MEMORY_TYPE.DIRECTORY, name:'test', content: [] });
expect(error.message).toBe('Already exists test');
expect(error.ref).toEqual({ $type: MEMORY_TYPE.DIRECTORY, name: 'test', content: [] });
});
});
2 changes: 1 addition & 1 deletion adapters/in-memory/src/exports/lib.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { source } from '../core/source';
import { PLUGIN_TYPE, type LoomSourceAdapter, Directory, LoomFile } from '@loom-io/core';

export default (key: string = 'file://') => ({
export default (key: string = 'memory://') => ({
$type: PLUGIN_TYPE.SOURCE_ADAPTER,
source: (link: string, Type?: typeof Directory | typeof LoomFile) => {
if(link.startsWith(key)) {
Expand Down
4 changes: 4 additions & 0 deletions adapters/in-memory/tests/source.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { testSource } from '@loom-io/interface-tests';
import InMemorySourceAdapter from '../src/exports/lib';

testSource('memory://', InMemorySourceAdapter());
4 changes: 2 additions & 2 deletions adapters/minio/src/core/source.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Directory, LoomFile } from '@loom-io/core/internal';
import Minio, { ClientOptions as S3Options} from 'minio';
import { Client as MinioClient, ClientOptions as S3Options} from 'minio';
import { Adapter } from './adapter';
import { dirname } from 'path';

export { S3Options };

export async function source (link: string, bucket: string, options: S3Options, Type?: typeof Directory | typeof LoomFile ): Promise<Directory | LoomFile>{
const minio = new Minio.Client(options);
const minio = new MinioClient(options);
const adapter = new Adapter(minio, bucket);
if(Type === Directory) {
return new Directory(adapter, link);
Expand Down
20 changes: 20 additions & 0 deletions adapters/minio/src/utils/typechecks.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { describe, test, expect } from 'vitest';
import { isMinioException } from './typechecks.js';
import { } from 'minio';

class MinioLikeException extends Error{
code: string;
message: string;
}


describe('typechecks', () => {
test('isMinioException', () => {
expect(isMinioException(new Error('test'))).toBe(false);
expect(isMinioException({})).toBe(false);
expect(isMinioException({ code: 'test' })).toBe(false);
expect(isMinioException({ message: 'test' })).toBe(false);
expect(isMinioException({ code: 'test', message: 'test' })).toBe(false);
expect(isMinioException(new MinioLikeException('test'))).toBe(true);
});
});
16 changes: 16 additions & 0 deletions adapters/minio/test/source.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { testSource } from '@loom-io/interface-tests';
import S3MinioSourceAdapter from '../src/exports/lib';

const s3config = {
endPoint: 'play.min.io',
port: 9000,
useSSL: true,
accessKey: 'Q3AM3UQ867SPQQA43P2F',
secretKey: 'zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG',
};


testSource('s3://', S3MinioSourceAdapter(undefined, {
bucket: 'test-bucket',
...s3config
}));
14 changes: 9 additions & 5 deletions adapters/node-fs/src/core/source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,16 @@ export const source = async (path: string, rootdir?: PathLike, Type?: typeof Dir
} else if(Type === Directory || path.endsWith('/')) {
return new Directory(adapter, path);
} else {
const stats = await fs.stat(path);
try {
const stats = await fs.stat(path);

if(stats.isFile()) {
const dir = new Directory(adapter, dirname(path));
return new LoomFile(adapter, dir, basename(path));
} else {
if(stats.isFile()) {
const dir = new Directory(adapter, dirname(path));
return new LoomFile(adapter, dir, basename(path));
} else {
return new Directory(adapter, path);
}
} catch {
return new Directory(adapter, path);
}
}
Expand Down
26 changes: 26 additions & 0 deletions adapters/node-fs/tests/source.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { testSource } from '@loom-io/interface-tests';
import FilesystemSourceAdapter from '../src/exports/lib';
import { describe, expect, test } from 'vitest';
import { source } from '../src/core/source';
import { LoomFile } from '@loom-io/core/internal';



testSource('file://', FilesystemSourceAdapter());

describe('Detail source test for node-fs adapter', () => {
test('should return a LoomFile for a existing file path', async () => {
const file = await source('adapters/node-fs/src/core/source.ts');
expect(file).toBeDefined();
expect(file).toBeInstanceOf(LoomFile);
expect(file).toHaveProperty('path', 'adapters/node-fs/src/core/source.ts');
});

test('should return a Directory for a existing directory path', async () => {
const dir = await source('adapters/node-fs/src/core');
expect(dir).toBeDefined();
expect(dir).not.toBeInstanceOf(LoomFile);
expect(dir).toHaveProperty('path', 'adapters/node-fs/src/core');
});

});
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@
],
"devDependencies": {
"@faker-js/faker": "^8.4.1",
"@types/node": "^20.11.28",
"@types/node": "^20.11.30",
"@typescript-eslint/eslint-plugin": "latest",
"@typescript-eslint/parser": "latest",
"@vitest/coverage-v8": "^1.4.0",
"eslint": "^8.57.0",
"publint": "^0.2.7",
"typescript": "^5.4.2",
"typescript": "^5.4.3",
"vitest": "^1.4.0"
},
"repository": {
Expand Down
6 changes: 3 additions & 3 deletions packages/base/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
"build": "tsc -p tsconfig.json"
},
"dependencies": {
"@loom-io/core": "workspace:^0.6.0",
"@loom-io/yamlConverter": "workspace:^0.6.0",
"@loom-io/jsonConverter": "workspace:^0.6.0"
"@loom-io/core": "workspace:^",
"@loom-io/jsonConverter": "workspace:^",
"@loom-io/yamlConverter": "workspace:^"
},
"exports": {
".": {
Expand Down
114 changes: 107 additions & 7 deletions packages/common/src/path.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,27 @@
import { describe, test, expect } from 'vitest';
import { addPrecedingAndTailingSlash, getSegmentsOfPath, getPathDepth, removePrecedingAndTrailingSlash, splitTailingPath } from './path';

import { addPrecedingAndTailingSlash, getUniqSegmentsOfPath, getSegmentsOfPath, getPathDepth, removePrecedingAndTrailingSlash, splitTailingPath, removePrecedingSlash, removeTailingSlash } from './path';

describe('path utils', async () => {

test('removePrecedingSlash', async () => {
expect(removePrecedingSlash('/test')).toBe('test');
expect(removePrecedingSlash('test')).toBe('test');
expect(removePrecedingSlash('/')).toBe('');
expect(removePrecedingSlash('')).toBe('');
expect(removePrecedingSlash('/test/some/slashing')).toBe('test/some/slashing');
});

test('removeTailingSlash', async () => {
expect(removeTailingSlash('/test/')).toBe('/test');
expect(removeTailingSlash('/test')).toBe('/test');
expect(removeTailingSlash('test/')).toBe('test');
expect(removeTailingSlash('test')).toBe('test');
expect(removeTailingSlash('/')).toBe('');
expect(removeTailingSlash('')).toBe('');
expect(removeTailingSlash('/test/tailing/sla_sigs-d/')).toBe('/test/tailing/sla_sigs-d');
});


test('removePresentedAndTrailingSlash', async () => {
expect(removePrecedingAndTrailingSlash('/test/')).toBe('test');
expect(removePrecedingAndTrailingSlash('/test')).toBe('test');
Expand Down Expand Up @@ -45,19 +64,100 @@ describe('path utils', async () => {
expect(getPathDepth('/test/some/slashing/long/with/file.txt')).toBe(6);
});

test('getFirstElementsOfPath', async () => {
test('getSegmentsOfPath', async () => {
expect(getSegmentsOfPath('/test/some/slashing/', 3)).toBe('/test/some/slashing/');
expect(getSegmentsOfPath('/test/some/slashing', 2)).toBe('/test/some');
expect(getSegmentsOfPath('/test/some/slashing', 2)).toBe('/test/some/');
expect(getSegmentsOfPath('test/some/slashing/', 2)).toBe('test/some/');
expect(getSegmentsOfPath('test/some/slashing', 3)).toBe('test/some/slashing');
expect(getSegmentsOfPath('/test/some/slashing/', 1)).toBe('/test/');
expect(getSegmentsOfPath('/test/some/slashing', 1)).toBe('/test');
expect(getSegmentsOfPath('/test/some/slashing', 1)).toBe('/test/');
expect(getSegmentsOfPath('test/some/slashing/', 1)).toBe('test/');
expect(getSegmentsOfPath('test/some/slashing', 1)).toBe('test');
expect(getSegmentsOfPath('/test/some/slashing/', 0)).toBe('/');
expect(getSegmentsOfPath('test/some/slashing', 1)).toBe('test/');
expect(getSegmentsOfPath('/cotton/slashing/', 0)).toBe('/');
expect(getSegmentsOfPath('/test/some/slashing', 0)).toBe('/');
expect(getSegmentsOfPath('test/some/slashing/', 0)).toBe('/');
expect(getSegmentsOfPath('test/some/slashing', 0)).toBe('/');
expect(getSegmentsOfPath('../../cotton-going-up/loop', 1)).toBe('../');
expect(getSegmentsOfPath('./relative/', 1)).toBe('./relative/');
expect(getSegmentsOfPath('./relative/cotton', 1)).toBe('./relative/');
expect(getSegmentsOfPath('./relative/cotton', 2)).toBe('./relative/cotton');
expect(getSegmentsOfPath('.hidden', 1)).toBe('.hidden');
});


test('getUniqSegmentsOfPath', async () => {
const paths = [
'/cotton/wool/silk',
'coding/programming/',
'coding/with/cotton/coding',
'/cotton/coding/is the best/',
'/some-thing/else',
'cotton-coding/programming',
'cotton-coding/is_not_belong_to_trees/maybe/sourceTrees',
'/coding/programming',
'./some_relative_path',
'some_relative_path',
'.hidden/file',
'/.hidden/at/root',
'a/long/.with/all/_po-si-ble/&symbols/and/numbers/1234567890',
];

expect(getUniqSegmentsOfPath(paths, 1)).toEqual([
'/cotton',
'coding',
'/some-thing',
'cotton-coding',
'/coding',
'some_relative_path',
'.hidden',
'/.hidden',
'a'
]);

expect(getUniqSegmentsOfPath(paths, 2)).toEqual([
'/cotton/wool',
'coding/programming',
'coding/with',
'/cotton/coding',
'/some-thing/else',
'cotton-coding/programming',
'cotton-coding/is_not_belong_to_trees',
'/coding/programming',
'some_relative_path',
'.hidden/file',
'/.hidden/at',
'a/long'
]);

expect(getUniqSegmentsOfPath(paths, 3)).toEqual([
'/cotton/wool/silk',
'coding/programming',
'coding/with/cotton',
'/cotton/coding/is the best',
'/some-thing/else',
'cotton-coding/programming',
'cotton-coding/is_not_belong_to_trees/maybe',
'/coding/programming',
'some_relative_path',
'.hidden/file',
'/.hidden/at/root',
'a/long/.with'
]);

expect(getUniqSegmentsOfPath(paths, 7)).toEqual([
'/cotton/wool/silk',
'coding/programming',
'coding/with/cotton/coding',
'/cotton/coding/is the best',
'/some-thing/else',
'cotton-coding/programming',
'cotton-coding/is_not_belong_to_trees/maybe/sourceTrees',
'/coding/programming',
'some_relative_path',
'.hidden/file',
'/.hidden/at/root',
'a/long/.with/all/_po-si-ble/&symbols/and'
]);
});

});
30 changes: 26 additions & 4 deletions packages/common/src/path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,24 +55,46 @@ export function getPathDepth(path: string): number {
return elements.length;
}


/**
* Take relative and fix paths. The function will ignore preceding, trailing slashes and relative slashes on the same level (./).
* If the return is a subpath it will be returned with a trailing slash.
* If depth is higher than the path depth, the function will return the path as it is given.
* @param path
* @param depth
* @returns
*/
export function getSegmentsOfPath(path: string, depth: number): string {
const isRelative = path.startsWith('./');
const firstIsSlash = path.startsWith('/');
const lastIsSlash = path.endsWith('/');
const parts = removePrecedingAndTrailingSlash(path).split('/');
if (depth === 0) {
return '/';
} else if (depth >= parts.length) {
} else if (depth >= parts.length || (isRelative && depth === parts.length - 1)) {
return path;
} else {
if(isRelative) {
return `${parts.slice(0, depth+1).join('/')}/`;
}
const subPath = parts.slice(0, depth).join('/');
return `${firstIsSlash ? '/' : ''}${subPath}${lastIsSlash ? '/' : ''}`;
return `${firstIsSlash ? '/' : ''}${subPath}/`;
}
}


/**
* Returns a list of unique paths with the given depth.
* Relative paths without preceding slash will be treated as relatives (./).
* To better compare preceding and trailing slashes are removed.
* @param paths
* @param depth
* @returns
*/
export function getUniqSegmentsOfPath(paths: string[], depth: number): string[] {
return Array.from(paths.reduce((acc, path) => {
path = path.startsWith('./') ? path.slice(2) : path;
const first = getSegmentsOfPath(path, depth);
acc.add(first);
acc.add(removeTailingSlash(first));
return acc;
}, new Set<string>()));
}
3 changes: 2 additions & 1 deletion packages/core/src/exceptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,9 @@ export class NoSourceAdapterException extends Error implements LoomException {
}

export class DirectoryNotEmptyException extends Error implements LoomException {

__loomExceptionRef = EXCEPTION_REF.DIRECTORY_NOT_EMPTY;
protected _path: string;
_path: string;
constructor(path: string) {
super(`Directory ${path} is not empty`);
this._path = path;
Expand Down
Loading

0 comments on commit 1af2566

Please sign in to comment.