Skip to content

Commit 2c830c8

Browse files
tomzo42tomzohar
andauthored
feat(workspace-manager): added get directory api (#68)
Co-authored-by: Tom Zohar <60091317+Tcodrz@users.noreply.github.com>
1 parent 50aab94 commit 2c830c8

File tree

9 files changed

+251
-1
lines changed

9 files changed

+251
-1
lines changed

apps/cli-daemon/src/app/app.module.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ import { AppService } from './app.service';
55
import { GeneratorsModule } from './generators/generators.module';
66
import { SessionService } from './session/session.service';
77
import { WorkspaceModule } from './workspace/workspace.module';
8+
import { WorkspaceManagerModule } from './workspace-manager/workspace-manager.module';
89

910
@Global()
1011
@Module({
11-
imports: [WorkspaceModule, GeneratorsModule],
12+
imports: [WorkspaceModule, GeneratorsModule, WorkspaceManagerModule],
1213
controllers: [AppController],
1314
providers: [AppService, SessionService],
1415
exports: [SessionService],
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { IsBoolean, IsOptional, IsString } from 'class-validator';
2+
3+
export class DirectoryDto {
4+
@IsString()
5+
name: string;
6+
7+
@IsBoolean()
8+
isNG: boolean;
9+
10+
@IsOptional()
11+
isFavorite: boolean;
12+
13+
constructor(name: string, isNG = false, isFavorite = false) {
14+
this.name = name;
15+
this.isNG = isNG;
16+
this.isFavorite = isFavorite;
17+
}
18+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const NOT_VALID_PATH_EXCEPTION = 'Not a valid path';
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './exception-messages';
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { InternalServerErrorException } from '@nestjs/common';
2+
import { Test, TestingModule } from '@nestjs/testing';
3+
4+
import { WorkspaceManagerController } from './workspace-manager.controller';
5+
import { WorkspaceManagerService } from './workspace-manager.service';
6+
7+
type WorkspaceManagerServiceMock = Record<
8+
keyof WorkspaceManagerService,
9+
jest.Mock
10+
>;
11+
const workspaceManagerMock: WorkspaceManagerServiceMock = {
12+
getDirectoriesInPath: jest.fn(),
13+
};
14+
15+
describe('WorkspaceManagerController', () => {
16+
let controller: WorkspaceManagerController;
17+
18+
beforeEach(async () => {
19+
const module: TestingModule = await Test.createTestingModule({
20+
controllers: [WorkspaceManagerController],
21+
providers: [
22+
{ provide: WorkspaceManagerService, useValue: workspaceManagerMock },
23+
],
24+
}).compile();
25+
26+
controller = module.get<WorkspaceManagerController>(
27+
WorkspaceManagerController
28+
);
29+
});
30+
31+
it('should be defined', () => {
32+
expect(controller).toBeDefined();
33+
});
34+
35+
describe('getDirectory', () => {
36+
it('should call workspaceManagerService.getDirectoriesInPath', async () => {
37+
await controller.getDirectory('abc');
38+
expect(workspaceManagerMock.getDirectoriesInPath).toHaveBeenCalledWith(
39+
'abc'
40+
);
41+
});
42+
43+
it('should throw InternalServerErrorException when workspaceManagerService.getDirectoriesInPath throws error', async () => {
44+
workspaceManagerMock.getDirectoriesInPath.mockImplementationOnce(() => {
45+
throw new Error();
46+
});
47+
try {
48+
await controller.getDirectory('');
49+
} catch (err) {
50+
expect(err).toEqual(new InternalServerErrorException());
51+
}
52+
});
53+
});
54+
});
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import {
2+
Controller,
3+
Get,
4+
InternalServerErrorException,
5+
Query,
6+
} from '@nestjs/common';
7+
8+
import { DirectoryDto } from './dto/directory.dto';
9+
import { WorkspaceManagerService } from './workspace-manager.service';
10+
11+
@Controller('workspace-manager')
12+
export class WorkspaceManagerController {
13+
constructor(
14+
private readonly workspaceManagerService: WorkspaceManagerService
15+
) {}
16+
17+
@Get('dir')
18+
public async getDirectory(
19+
@Query('path') path: string
20+
): Promise<DirectoryDto[]> {
21+
try {
22+
return this.workspaceManagerService.getDirectoriesInPath(path);
23+
} catch (err) {
24+
throw new InternalServerErrorException();
25+
}
26+
}
27+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { Module } from '@nestjs/common';
2+
3+
import { WorkspaceManagerController } from './workspace-manager.controller';
4+
import { WorkspaceManagerService } from './workspace-manager.service';
5+
6+
@Module({
7+
controllers: [WorkspaceManagerController],
8+
providers: [WorkspaceManagerService],
9+
})
10+
export class WorkspaceManagerModule {}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { Dirent } from 'fs';
2+
import fs from 'fs/promises';
3+
4+
import { BadRequestException } from '@nestjs/common';
5+
import { Test, TestingModule } from '@nestjs/testing';
6+
7+
import { SessionService } from '../session/session.service';
8+
9+
import { DirectoryDto } from './dto/directory.dto';
10+
import { NOT_VALID_PATH_EXCEPTION } from './entities';
11+
import { WorkspaceManagerService } from './workspace-manager.service';
12+
13+
jest.mock('fs/promises', () => ({
14+
readdir: jest.fn().mockImplementation(
15+
(path: string) =>
16+
new Promise((resolve, reject) => {
17+
if (path === 'error path') reject('invalid');
18+
resolve(mockDirectory);
19+
})
20+
),
21+
}));
22+
23+
const mockDirectory: Partial<Dirent>[] = [
24+
{ name: 'a', isDirectory: () => true },
25+
{ name: 'b', isDirectory: () => false },
26+
];
27+
28+
type SessionServiceMock = Partial<Record<keyof SessionService, jest.Mock>>;
29+
30+
describe('WorkspaceManagerService', () => {
31+
let service: WorkspaceManagerService;
32+
let sessionServiceMock: SessionServiceMock;
33+
34+
beforeEach(async () => {
35+
sessionServiceMock = {
36+
cwd: jest.fn().mockReturnValue('users/home')(),
37+
};
38+
39+
const module: TestingModule = await Test.createTestingModule({
40+
providers: [
41+
WorkspaceManagerService,
42+
{ provide: SessionService, useValue: sessionServiceMock },
43+
],
44+
}).compile();
45+
46+
service = module.get<WorkspaceManagerService>(WorkspaceManagerService);
47+
});
48+
49+
it('should be defined', () => {
50+
expect(service).toBeDefined();
51+
});
52+
53+
describe('getDirectoriesInPath', () => {
54+
it('should call fs.readdir', () => {
55+
service.getDirectoriesInPath('mock path');
56+
expect(fs.readdir).toHaveBeenCalledWith('mock path', {
57+
withFileTypes: true,
58+
});
59+
});
60+
61+
it('should return list of directories from a provided path', async () => {
62+
const directories = await service.getDirectoriesInPath('mock path');
63+
expect(directories).toEqual([new DirectoryDto('a', false)]);
64+
});
65+
66+
it('should call readdir with root path when not provided a path', async () => {
67+
await service.getDirectoriesInPath();
68+
expect(fs.readdir).toHaveBeenCalledWith('users/home', {
69+
withFileTypes: true,
70+
});
71+
});
72+
73+
it('should throw BadRequestException when called with invalid path', async () => {
74+
try {
75+
await service.getDirectoriesInPath('error path');
76+
} catch (err) {
77+
expect(err).toEqual(
78+
new BadRequestException(`${NOT_VALID_PATH_EXCEPTION}: error path`)
79+
);
80+
}
81+
});
82+
});
83+
});
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import fs from 'fs/promises';
2+
3+
import { NodeJsSyncHost } from '@angular-devkit/core/node';
4+
import { createWorkspaceHost } from '@angular-devkit/core/src/workspace';
5+
import { readWorkspace as devKitReadWorkspace } from '@angular-devkit/core/src/workspace/core';
6+
import { BadRequestException, Injectable } from '@nestjs/common';
7+
8+
import { SessionService } from '../session/session.service';
9+
10+
import { DirectoryDto } from './dto/directory.dto';
11+
import { NOT_VALID_PATH_EXCEPTION } from './entities';
12+
13+
@Injectable()
14+
export class WorkspaceManagerService {
15+
constructor(private sessionService: SessionService) {}
16+
17+
public async getDirectoriesInPath(
18+
path: string = this.sessionService.cwd
19+
): Promise<DirectoryDto[]> {
20+
try {
21+
const filesAndDirectories = await fs.readdir(path, {
22+
withFileTypes: true,
23+
});
24+
return Promise.all(
25+
filesAndDirectories
26+
.filter((fileOrFolder) => fileOrFolder.isDirectory())
27+
.map(
28+
async (directory) =>
29+
new DirectoryDto(
30+
directory.name,
31+
await this.isAngularWorkspace(directory.name, path)
32+
)
33+
)
34+
);
35+
} catch (e) {
36+
throw new BadRequestException(`${NOT_VALID_PATH_EXCEPTION}: ${path}`);
37+
}
38+
}
39+
40+
private async isAngularWorkspace(
41+
directoryName: string,
42+
path: string
43+
): Promise<boolean> {
44+
const fullPath = `${path}/${directoryName}`;
45+
try {
46+
const workspace = await devKitReadWorkspace(
47+
fullPath,
48+
createWorkspaceHost(new NodeJsSyncHost())
49+
);
50+
return !!workspace;
51+
} catch (e) {
52+
return false;
53+
}
54+
}
55+
}

0 commit comments

Comments
 (0)