diff --git a/README.md b/README.md
index ef92c8f..b0b773a 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@ This repository hosts the frontend service implementation of the Playground.
To start the playground using Docker, run:
```bash
-docker-compose up
+docker-compose upg
```
## Prerequisites
@@ -68,13 +68,13 @@ services:
backend:
build:
context: .
- dockerfile: Dockerfile_Backend
+ dockerfile: PlaygroundMonoDockerfile
ports:
- '8080:8080'
depends_on:
- dicedb
environment:
- - DICE_ADDR=dicedb:7379
+ - DICEDB_ADDR=dicedb:7379
```
2. Run the following command to start the backend server and DiceDB:
@@ -123,8 +123,9 @@ To generate a static production build of your Next.js application, follow these
Ensure that you have the following line in your `next.config.mjs` file:
```javascript
- output: 'export'
+ output: 'export';
```
+
2. **Build the Project:**
Run the following command in your terminal:
@@ -132,6 +133,7 @@ To generate a static production build of your Next.js application, follow these
```bash
npm run build
```
+
3. **Testing static build locally:**
```bash
npx serve@latest out
@@ -188,6 +190,6 @@ Contributors can join the [Discord Server](https://discord.gg/6r8uXWtXh7) for qu
## Contributors
-
-
+
+
diff --git a/package-lock.json b/package-lock.json
index bed3c30..e54735e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -38,6 +38,7 @@
"postcss": "^8",
"prettier": "^3.3.3",
"tailwindcss": "^3.4.1",
+ "ts-jest": "^29.2.5",
"ts-node": "^10.9.2",
"typescript": "^5"
}
@@ -2899,6 +2900,12 @@
"integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==",
"dev": true
},
+ "node_modules/async": {
+ "version": "3.2.6",
+ "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
+ "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
+ "dev": true
+ },
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@@ -3178,6 +3185,18 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
+ "node_modules/bs-logger": {
+ "version": "0.2.6",
+ "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz",
+ "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==",
+ "dev": true,
+ "dependencies": {
+ "fast-json-stable-stringify": "2.x"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/bser": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz",
@@ -3973,6 +3992,21 @@
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
"dev": true
},
+ "node_modules/ejs": {
+ "version": "3.1.10",
+ "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
+ "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==",
+ "dev": true,
+ "dependencies": {
+ "jake": "^10.8.5"
+ },
+ "bin": {
+ "ejs": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/electron-to-chromium": {
"version": "1.5.31",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.31.tgz",
@@ -4853,6 +4887,36 @@
"node": "^10.12.0 || >=12.0.0"
}
},
+ "node_modules/filelist": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz",
+ "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==",
+ "dev": true,
+ "dependencies": {
+ "minimatch": "^5.0.1"
+ }
+ },
+ "node_modules/filelist/node_modules/brace-expansion": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/filelist/node_modules/minimatch": {
+ "version": "5.1.6",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
+ "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
@@ -6105,6 +6169,24 @@
"@pkgjs/parseargs": "^0.11.0"
}
},
+ "node_modules/jake": {
+ "version": "10.9.2",
+ "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz",
+ "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==",
+ "dev": true,
+ "dependencies": {
+ "async": "^3.2.3",
+ "chalk": "^4.0.2",
+ "filelist": "^1.0.4",
+ "minimatch": "^3.1.2"
+ },
+ "bin": {
+ "jake": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/jest": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz",
@@ -7217,6 +7299,12 @@
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
},
+ "node_modules/lodash.memoize": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
+ "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==",
+ "dev": true
+ },
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@@ -9756,6 +9844,66 @@
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
"dev": true
},
+ "node_modules/ts-jest": {
+ "version": "29.2.5",
+ "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.5.tgz",
+ "integrity": "sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA==",
+ "dev": true,
+ "dependencies": {
+ "bs-logger": "^0.2.6",
+ "ejs": "^3.1.10",
+ "fast-json-stable-stringify": "^2.1.0",
+ "jest-util": "^29.0.0",
+ "json5": "^2.2.3",
+ "lodash.memoize": "^4.1.2",
+ "make-error": "^1.3.6",
+ "semver": "^7.6.3",
+ "yargs-parser": "^21.1.1"
+ },
+ "bin": {
+ "ts-jest": "cli.js"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0"
+ },
+ "peerDependencies": {
+ "@babel/core": ">=7.0.0-beta.0 <8",
+ "@jest/transform": "^29.0.0",
+ "@jest/types": "^29.0.0",
+ "babel-jest": "^29.0.0",
+ "jest": "^29.0.0",
+ "typescript": ">=4.3 <6"
+ },
+ "peerDependenciesMeta": {
+ "@babel/core": {
+ "optional": true
+ },
+ "@jest/transform": {
+ "optional": true
+ },
+ "@jest/types": {
+ "optional": true
+ },
+ "babel-jest": {
+ "optional": true
+ },
+ "esbuild": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/ts-jest/node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/ts-node": {
"version": "10.9.2",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
diff --git a/package.json b/package.json
index daff1dc..b55f7de 100644
--- a/package.json
+++ b/package.json
@@ -44,6 +44,7 @@
"postcss": "^8",
"prettier": "^3.3.3",
"tailwindcss": "^3.4.1",
+ "ts-jest": "^29.2.5",
"ts-node": "^10.9.2",
"typescript": "^5"
},
diff --git a/src/lib/__tests__/api.test.ts b/src/lib/__tests__/api.test.ts
new file mode 100644
index 0000000..deae385
--- /dev/null
+++ b/src/lib/__tests__/api.test.ts
@@ -0,0 +1,93 @@
+import { executeShellCommandOnServer } from '../api';
+import { WebService } from '@/services/webServices';
+
+// Mock WebService
+jest.mock('@/services/webServices', () => ({
+ WebService: {
+ post: jest.fn(),
+ },
+}));
+
+describe('executeShellCommandOnServer', () => {
+ const mockCmd = 'testCommand';
+ const mockCmdOptions = { option1: 'value1' };
+ const mockCmdExecURL = `/shell/exec/${mockCmd}`;
+ let consoleErrorSpy: jest.SpyInstance;
+
+ beforeEach(() => {
+ consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
+ });
+
+ afterEach(() => {
+ consoleErrorSpy.mockRestore();
+ jest.clearAllMocks();
+ });
+
+ it('should return data when WebService.post is successful', async () => {
+ const mockResponse = { data: 'Success Response' };
+ (WebService.post as jest.Mock).mockResolvedValueOnce(mockResponse);
+
+ const result = await executeShellCommandOnServer(mockCmd, mockCmdOptions);
+
+ expect(WebService.post).toHaveBeenCalledWith(
+ mockCmdExecURL,
+ mockCmdOptions,
+ );
+ expect(result).toEqual('Success Response');
+ });
+
+ it('should return error message when response structure is unexpected', async () => {
+ const mockResponse = {}; // Simulate unexpected response structure
+ (WebService.post as jest.Mock).mockResolvedValueOnce(mockResponse);
+
+ const result = await executeShellCommandOnServer(mockCmd, mockCmdOptions);
+
+ expect(result).toBe('Error: Error: Unexpected response structure');
+ expect(WebService.post).toHaveBeenCalledWith(
+ mockCmdExecURL,
+ mockCmdOptions,
+ );
+ });
+
+ it('should return error message when WebService.post throws an error', async () => {
+ const mockError = new Error('Network Error');
+
+ (WebService.post as jest.Mock).mockRejectedValueOnce(mockError);
+ const result = await executeShellCommandOnServer(mockCmd, mockCmdOptions);
+ expect(WebService.post).toHaveBeenCalledWith(
+ mockCmdExecURL,
+ mockCmdOptions,
+ );
+ expect(result).toBe(`Error: ${mockError}`);
+ consoleErrorSpy.mockRestore();
+ });
+
+ it('should log error to the console when WebService.post throws an error', async () => {
+ const mockError = new Error('Request Failed');
+ const consoleSpy = jest
+ .spyOn(console, 'error')
+ .mockImplementation(() => {});
+ (WebService.post as jest.Mock).mockRejectedValueOnce(mockError);
+
+ await executeShellCommandOnServer(mockCmd, mockCmdOptions);
+
+ expect(consoleSpy).toHaveBeenCalledWith(
+ 'Error executing command:',
+ mockError,
+ );
+ consoleSpy.mockRestore();
+ });
+
+ it('should always include the cmd parameter in the URL', async () => {
+ const mockResponse = { data: 'Some Response' };
+ (WebService.post as jest.Mock).mockResolvedValueOnce(mockResponse);
+
+ const result = await executeShellCommandOnServer(mockCmd, mockCmdOptions);
+
+ expect(WebService.post).toHaveBeenCalledWith(
+ expect.stringContaining(`/shell/exec/${mockCmd}`),
+ mockCmdOptions,
+ );
+ expect(result).toEqual('Some Response');
+ });
+});
diff --git a/src/lib/api.ts b/src/lib/api.ts
index 1bea792..00ab6c0 100644
--- a/src/lib/api.ts
+++ b/src/lib/api.ts
@@ -5,8 +5,10 @@ export const executeShellCommandOnServer = async (
cmd: string,
cmdOptions: object,
) => {
+ const cmdExecURL = `/shell/exec/${cmd}`;
+
try {
- const response = await WebService.post(`/shell/exec`, cmdOptions);
+ const response = await WebService.post(cmdExecURL, cmdOptions);
if (response?.data) {
return response.data;
} else {
diff --git a/src/services/webServices.ts b/src/services/webServices.ts
index f721a73..436c323 100644
--- a/src/services/webServices.ts
+++ b/src/services/webServices.ts
@@ -2,7 +2,7 @@ let PLAYGROUND_MONO_URL = process.env.NEXT_PUBLIC_PLAYGROUND_MONO_URL;
if (!PLAYGROUND_MONO_URL) {
console.warn(
- 'Warning: NEXT_PUBLIC_BACKEND_URL is not defined. Defaulting to http://localhost:3000',
+ 'Warning: NEXT_PUBLIC_PLAYGROUND_MONO_URL is not defined. Defaulting to http://localhost:8080',
);
PLAYGROUND_MONO_URL = 'http://localhost:8080';
}
@@ -41,10 +41,16 @@ export const WebService = {
try {
const response = await fetch(`${PLAYGROUND_MONO_URL}${url}`, options);
+ if (!response) {
+ throw new Error('No response received from the server.');
+ }
+
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
+
const result = await response.json();
+
return result;
} catch (error) {
if (error instanceof Error)