diff --git a/apps/playground-web/components/CLI/CLI.tsx b/apps/playground-web/components/CLI/CLI.tsx deleted file mode 100644 index c4ad381..0000000 --- a/apps/playground-web/components/CLI/CLI.tsx +++ /dev/null @@ -1,52 +0,0 @@ -// src/components/CLI/CLI.tsx -'use client'; - -// hooks -import { useCli } from './hooks/useCli'; - -interface CliProps { - decreaseCommandsLeft: () => void; -} - -export default function Cli({ decreaseCommandsLeft }: CliProps) { - const { - handleInputChange, - handleKeyDown, - terminalRef, - inputRef, - output, - command, - } = useCli(decreaseCommandsLeft); - return ( -
inputRef.current?.focus()} - > - {output.map((line, index) => ( -
- {line} -
- ))} -
-

dice ~$

-
- -
-
-
- ); -} diff --git a/apps/playground-web/components/CLI/__tests__/index.test.tsx b/apps/playground-web/components/CLI/__tests__/index.test.tsx deleted file mode 100644 index 7500faa..0000000 --- a/apps/playground-web/components/CLI/__tests__/index.test.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import '@testing-library/jest-dom'; -import { render, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import CLI from '../CLI'; - -const decreaseCommandsLeftMock = jest.fn(); - -const dummyCommands = [ - 'set abc 100', - 'get abc', - 'set xyz 200', - 'get xyz', - 'set pqr 300', - 'get pqr', -]; - -const setupTest = () => { - const user = userEvent.setup(); - const utils = render(); - - const terminalElement = screen.getByTestId('terminal'); - const cliInputElement = screen.getByTestId('cli-input'); - - const typeMultipleCommands = async () => { - for (const command of dummyCommands) { - await user.type(cliInputElement, `${command}{enter}`); - } - }; - - return { - terminalElement, - cliInputElement, - user, - typeMultipleCommands, - ...utils, - }; -}; - -describe('CLI Component', () => { - it('should focus on terminal element click', async () => { - const { terminalElement, cliInputElement, user } = setupTest(); - await user.click(terminalElement); - expect(cliInputElement).toHaveFocus(); - }); - - it('should update values when new value is typed', async () => { - const { cliInputElement, user } = setupTest(); - - // type a command and check if the value is updated - await user.type(cliInputElement, 'set abc'); - expect(cliInputElement.value).toBe('set abc'); - - await user.type(cliInputElement, '{enter}'); - expect(cliInputElement.value).toBe(''); - }); - - it('should throw error when user types blacklisted command', async () => { - const { cliInputElement, user, getByTestId } = setupTest(); - - await user.type(cliInputElement, 'EXEC{enter}'); - const terminalOutputElement = getByTestId('terminal-output'); - expect(terminalOutputElement).toHaveTextContent( - "(error) ERR unknown command 'EXEC'", - ); - }); - - it('should navigate through command history', async () => { - const { cliInputElement, user, typeMultipleCommands } = setupTest(); - - await typeMultipleCommands(); - expect(cliInputElement.value).toBe(''); - - await user.keyboard('[ArrowUp]'); - expect(cliInputElement.value).toBe(dummyCommands[dummyCommands.length - 1]); - - await user.keyboard('[ArrowUp]'); - expect(cliInputElement.value).toBe(dummyCommands[dummyCommands.length - 2]); - - await user.keyboard('[ArrowDown]'); - await user.keyboard('[ArrowDown]'); - expect(cliInputElement.value).toBe(''); - }); - - it('should navigate through command history based on current user input', async () => { - const { cliInputElement, user, typeMultipleCommands } = setupTest(); - await typeMultipleCommands(); - expect(cliInputElement.value).toBe(''); - - const newCommand = 'set'; - await user.type(cliInputElement, newCommand); - const filteredCommands = dummyCommands.filter((cmd) => - cmd.startsWith(newCommand), - ); - - await user.keyboard('[ArrowUp]'); - expect(cliInputElement.value).toBe( - filteredCommands[filteredCommands.length - 1], - ); - - await user.keyboard('[ArrowUp]'); - expect(cliInputElement.value).toBe( - filteredCommands[filteredCommands.length - 2], - ); - - // check whether typed command is accessible - await user.keyboard('[ArrowDown]'); - await user.keyboard('[ArrowDown]'); - expect(cliInputElement.value).toBe(newCommand); - }); -}); diff --git a/apps/playground-web/components/CLI/hooks/useCli.tsx b/apps/playground-web/components/CLI/hooks/useCli.tsx deleted file mode 100644 index c6d28f7..0000000 --- a/apps/playground-web/components/CLI/hooks/useCli.tsx +++ /dev/null @@ -1,124 +0,0 @@ -// react -import { useState, useEffect, useRef, KeyboardEvent, ChangeEvent } from 'react'; - -// utils -import { handleCommand } from '@/shared/utils/cliUtils'; -import blocklistedCommands from '@/shared/utils/blocklist'; // Assuming you added blocklist here - -export const useCli = (decreaseCommandsLeft: () => void) => { - // states - const [command, setCommand] = useState(''); - const [output, setOutput] = useState([]); - const [tempCommand, setTempCommand] = useState(''); - //Initialise the command history with sessionStorage - const [commandHistory, setCommandHistory] = useState([]); - const [historyIndex, setHistoryIndex] = useState(-1); - - // useRefs - const terminalRef = useRef(null); - const inputRef = useRef(null); - - const handleCommandWrapper = () => { - const commandName = command.trim().split(' ')[0]?.toUpperCase() ?? ''; // Extract the command - - if (blocklistedCommands.includes(commandName)) { - setOutput((prevOutput) => [ - ...prevOutput, - `(error) ERR unknown command '${commandName}'`, - ]); - } else { - handleCommand({ command, setOutput }); // Execute if not blocklisted - } - - setCommand(''); // Clear input - decreaseCommandsLeft(); // Call to update remaining commands - }; - - useEffect(() => { - if (terminalRef.current) { - terminalRef.current.scrollTop = terminalRef.current.scrollHeight; - } - }, [output]); - - //Load initial command history if present - useEffect(() => { - const savedHistory = sessionStorage.getItem('terminalHistory'); - const commandHistory = savedHistory ? JSON.parse(savedHistory) : []; - setCommandHistory(commandHistory); - }, []); - - // Save to session storage whenever commandHistory changes - useEffect(() => { - sessionStorage.setItem('terminalHistory', JSON.stringify(commandHistory)); - }, [commandHistory]); - - useEffect(() => { - if (inputRef.current) { - inputRef.current.focus(); - } - }, []); - - const handleInputChange = (e: ChangeEvent) => { - setCommand(e.target.value); - // Save current input when starting to navigate history - setTempCommand(e.target.value); - }; - - const handleKeyDown = (e: KeyboardEvent) => { - if (e.key === 'Enter') { - handleCommandWrapper(); - if (command.trim().length !== 0) { - setCommandHistory((prev) => [...prev, command]); - setHistoryIndex(-1); - } - return; - } - - const filteredCommandHistory = commandHistory.filter((cmd) => { - return cmd.startsWith(tempCommand); - }); - - if (e.key === 'ArrowUp') { - e.preventDefault(); - if (historyIndex < filteredCommandHistory.length - 1) { - if (historyIndex === -1) { - // Save current input when starting to navigate history - setTempCommand(command); - } - const newIndex = historyIndex + 1; - setHistoryIndex(newIndex); - setCommand( - filteredCommandHistory[filteredCommandHistory.length - 1 - newIndex]!, - ); - } - } else if (e.key === 'ArrowDown') { - e.preventDefault(); - if (historyIndex > -1) { - const newIndex = historyIndex - 1; - setHistoryIndex(newIndex); - if (newIndex === -1) { - // Restore the saved input when reaching the bottom - setCommand(tempCommand); - } else { - setCommand( - filteredCommandHistory[ - filteredCommandHistory.length - 1 - newIndex - ]!, - ); - } - } - } - }; - - return { - handleInputChange, - handleKeyDown, - terminalRef, - inputRef, - output, - setOutput, - command, - tempCommand, - setTempCommand, - }; -}; diff --git a/apps/playground-web/components/Playground/TerminalUI.tsx b/apps/playground-web/components/Playground/TerminalUI.tsx index 949bd17..a9217b2 100644 --- a/apps/playground-web/components/Playground/TerminalUI.tsx +++ b/apps/playground-web/components/Playground/TerminalUI.tsx @@ -6,9 +6,10 @@ import { useState } from 'react'; import Tooltip from '../Overlays/Tooltip'; export function TerminalUI({ initialCommandsLeft = 1000 }) { const [commandsLeft, setCommandsLeft] = useState(initialCommandsLeft); - const decreaseCommandsLeft = () => { - setCommandsLeft((prev) => (prev > 0 ? prev - 1 : 0)); + const handleCommandExecuted = (commands: number) => { + setCommandsLeft(commands); }; + return ( <>
@@ -23,7 +24,7 @@ export function TerminalUI({ initialCommandsLeft = 1000 }) { className="h-64 md:h-[30rem] bg-gray-100 rounded-lg overflow-hidden shadow-md" data-testid="shell-container" > - +
diff --git a/apps/playground-web/components/Playground/__tests__/TerminalUI.test.tsx b/apps/playground-web/components/Playground/__tests__/TerminalUI.test.tsx index eaa6b5f..1ef04af 100644 --- a/apps/playground-web/components/Playground/__tests__/TerminalUI.test.tsx +++ b/apps/playground-web/components/Playground/__tests__/TerminalUI.test.tsx @@ -4,17 +4,16 @@ import userEvent from '@testing-library/user-event'; import { TerminalUI } from '../TerminalUI'; import { formatTime } from '@/shared/utils/commonUtils'; +// Shared variable to store initialCommandsLeft +let sharedInitialCommandsLeft = 1000; + jest.mock('@/shared/hooks/useTimer', () => ({ useTimer: jest.fn(() => ({ timeLeft: 15 * 60 })), // Mock 15 minutes })); -jest.mock('@/shared/utils/commonUtils', () => ({ - formatTime: jest.fn( - (seconds) => `${Math.floor(seconds / 60)}:${seconds % 60}`, - ), -})); - const setupTest = (initialCommandsLeft = 1000) => { + sharedInitialCommandsLeft = initialCommandsLeft; + const user = userEvent.setup(); const utils = render( , @@ -39,6 +38,21 @@ const setupTest = (initialCommandsLeft = 1000) => { }; }; +jest.mock('@/shared/utils/commonUtils', () => ({ + formatTime: jest.fn( + (seconds) => `${Math.floor(seconds / 60)}:${seconds % 60}`, + ), + handleResult: jest.fn( + ({ result, newOutput, setOutput, onCommandExecuted }) => { + const mockData = result?.body?.data ? result.body.data : 'mock error'; + setOutput([newOutput, mockData]); + + const commandsLeft = Math.max(0, sharedInitialCommandsLeft - 1); + onCommandExecuted(commandsLeft); + }, + ), +})); + describe('TerminalUI Component', () => { it('renders TerminalUI', () => { const { terminalContainer, diceIcons, shellContainer, cliInputElement } = diff --git a/apps/playground-web/components/Shell/Shell.tsx b/apps/playground-web/components/Shell/Shell.tsx index 47f320b..643a9a3 100644 --- a/apps/playground-web/components/Shell/Shell.tsx +++ b/apps/playground-web/components/Shell/Shell.tsx @@ -5,10 +5,10 @@ import { useShell } from './hooks/useShell'; interface ShellProps { - decreaseCommandsLeft: () => void; + onCommandExecuted: (commandsLeft: number) => void; } -export default function Shell({ decreaseCommandsLeft }: ShellProps) { +export default function Shell({ onCommandExecuted }: ShellProps) { const { handleInputChange, handleKeyDown, @@ -16,7 +16,7 @@ export default function Shell({ decreaseCommandsLeft }: ShellProps) { inputRef, output, command, - } = useShell(decreaseCommandsLeft); + } = useShell(onCommandExecuted); return (
{ const user = userEvent.setup(); - const utils = render( - , - ); + const utils = render(); const terminalElement = screen.getByTestId('terminal'); const cliInputElement = screen.getByTestId('shell-input'); diff --git a/apps/playground-web/components/Shell/hooks/useShell.tsx b/apps/playground-web/components/Shell/hooks/useShell.tsx index 846a68b..5da2699 100644 --- a/apps/playground-web/components/Shell/hooks/useShell.tsx +++ b/apps/playground-web/components/Shell/hooks/useShell.tsx @@ -5,7 +5,7 @@ import { useState, useEffect, useRef, KeyboardEvent, ChangeEvent } from 'react'; import { handleCommand } from '@/shared/utils/shellUtils'; import blocklistedCommands from '@/shared/utils/blocklist'; -export const useShell = (decreaseCommandsLeft: () => void) => { +export const useShell = (onCommandExecuted: (commandsLeft: number) => void) => { // states const [command, setCommand] = useState(''); const [output, setOutput] = useState([]); @@ -32,11 +32,10 @@ export const useShell = (decreaseCommandsLeft: () => void) => { `(error) ERR unknown command '${commandName}'`, ]); } else { - handleCommand({ command, setOutput }); // Execute if not blocklisted + handleCommand({ command, setOutput, onCommandExecuted }); // Execute if not blocklisted } setCommand(''); // Clear input - decreaseCommandsLeft(); // Call to update remaining commands }; useEffect(() => { diff --git a/apps/playground-web/lib/api.ts b/apps/playground-web/lib/api.ts index 1e3931e..3a6e916 100644 --- a/apps/playground-web/lib/api.ts +++ b/apps/playground-web/lib/api.ts @@ -1,26 +1,28 @@ // src/lib/api.ts import { WebService } from '@/services/webServices'; +import { MonoResponse } from './monoResponse'; + export const executeShellCommandOnServer = async ( cmd: string, cmdOptions: object, -) => { +): Promise<{ headers: { [key: string]: string }; body: MonoResponse }> => { const cmdExecURL = `/shell/exec/${cmd}`; try { const response = await WebService.post(cmdExecURL, cmdOptions); // Check if the response contains data or if it's an error response - if (response?.data) { - return response.data; - } else if (response?.error) { - return response.error; + if (response?.body?.data) { + return response; + } else if (response?.body?.error) { + return response; } else { throw new Error('Unexpected response structure'); } } catch (error) { // Propagate the error from the backend exactly as it is console.error('Error executing command:', error); - return `${error}`; // Just return the error message directly + throw new Error(`${error}`); } }; diff --git a/apps/playground-web/lib/monoResponse.ts b/apps/playground-web/lib/monoResponse.ts new file mode 100644 index 0000000..f520a05 --- /dev/null +++ b/apps/playground-web/lib/monoResponse.ts @@ -0,0 +1,4 @@ +export interface MonoResponse { + data: string | null; + error: string | null; +} diff --git a/apps/playground-web/lib/monoSchema.ts b/apps/playground-web/lib/monoSchema.ts new file mode 100644 index 0000000..16651e1 --- /dev/null +++ b/apps/playground-web/lib/monoSchema.ts @@ -0,0 +1,13 @@ +import { z } from 'zod'; + +const BodySchema = z.object({ + data: z.string(), +}); + +// Define the schema for an error response +const ErrorSchema = z.object({ + error: z.string(), +}); + +// Combine the schemas +export const MonoSchema = z.union([BodySchema, ErrorSchema]); diff --git a/apps/playground-web/package.json b/apps/playground-web/package.json index 1482284..4204b69 100644 --- a/apps/playground-web/package.json +++ b/apps/playground-web/package.json @@ -17,6 +17,7 @@ "test:coverage": "jest --coverage" }, "dependencies": { + "@dicedb/ui": "workspace:*", "@emotion/react": "^11.13.3", "@emotion/styled": "^11.13.0", "@mui/icons-material": "^6.1.1", @@ -29,9 +30,12 @@ "react-dom": "^18", "react-terminal-ui": "^1.3.0", "sharp": "^0.33.5", - "@dicedb/ui": "workspace:*" + "zod": "^3.23.8" }, "devDependencies": { + "@dicedb/eslint-config": "workspace:*", + "@dicedb/tailwind-config": "workspace:*", + "@dicedb/typescript-config": "workspace:*", "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.5.0", "@testing-library/react": "^16.0.1", @@ -48,9 +52,6 @@ "prettier": "^3.3.3", "tailwindcss": "^3.4.1", "ts-node": "^10.9.2", - "typescript": "^5", - "@dicedb/typescript-config": "workspace:*", - "@dicedb/eslint-config": "workspace:*", - "@dicedb/tailwind-config": "workspace:*" + "typescript": "^5" } } diff --git a/apps/playground-web/services/webServices.ts b/apps/playground-web/services/webServices.ts index 77294c1..63c7461 100644 --- a/apps/playground-web/services/webServices.ts +++ b/apps/playground-web/services/webServices.ts @@ -1,3 +1,7 @@ +import { MonoSchema } from '@/lib/monoSchema'; + +import { MonoResponse } from '@/lib/monoResponse'; + let PLAYGROUND_MONO_URL = process.env.NEXT_PUBLIC_PLAYGROUND_MONO_URL; if (!PLAYGROUND_MONO_URL) { @@ -24,7 +28,10 @@ export const WebService = { // eslint-disable-next-line @typescript-eslint/no-explicit-any data: any = null, headers: HeadersType, - ) => { + ): Promise<{ + headers: { [key: string]: string }; + body: MonoResponse; + }> => { const options: RequestOptions = { method, headers: { @@ -41,24 +48,45 @@ export const WebService = { try { const response = await fetch(`${PLAYGROUND_MONO_URL}${url}`, options); - // If the response is not OK, check if the response contains error information - if (!response.ok) { - const errorResponse = await response.json(); - if (errorResponse?.error) { - throw errorResponse.error; - } else { - throw new Error(`HTTP error! Status: ${response.status}`); - } - } + const headers: { [key: string]: string } = {}; + response?.headers?.forEach((value: string, key: string) => { + headers[key] = value; + }); // Parse the result as JSON - const result = await response.json(); - return result; + const body = await response.json(); + const parsedBody = MonoSchema.safeParse(body); + if (!parsedBody.success) { + console.error('Invalid response data:', parsedBody.error); + return { + headers: headers, + body: { data: null, error: parsedBody?.error?.message }, + }; + } + + if ('data' in parsedBody.data) { + return { + headers: headers, + body: { data: parsedBody?.data?.data, error: null }, + }; + } else { + return { + headers: headers, + body: { data: null, error: parsedBody.data.error }, + }; + } } catch (error) { if (error instanceof Error) { console.error(`Error with ${method} request: ${error.message}`); + return { + headers: headers, + body: { error: `${error.message}`, data: null }, + }; } - throw error; + return { + headers: headers, + body: { error: 'Unknown error occurred', data: null }, + }; } }, diff --git a/apps/playground-web/shared/utils/cliUtils.ts b/apps/playground-web/shared/utils/cliUtils.ts deleted file mode 100644 index 5d967ca..0000000 --- a/apps/playground-web/shared/utils/cliUtils.ts +++ /dev/null @@ -1,79 +0,0 @@ -// src/shared/utils/cliUtils.ts - -import { executeShellCommandOnServer } from '@/lib/api'; -import { CommandHandler } from '@/types'; - -export const handleCommand = async ({ command, setOutput }: CommandHandler) => { - const newOutput = `dice > ${command}`; - let result: string; - - const [cmd, ...args] = command.split(' '); - if (!cmd) { - result = 'Invalid command'; - setOutput((prevOutput) => [...prevOutput, newOutput, result]); - return; - } - - switch (cmd.toUpperCase()) { - case 'GET': - if (args.length < 1) { - result = 'Invalid command. Usage: GET key'; - setOutput((prevOutput) => [...prevOutput, newOutput, result]); - return; - } - - try { - const [key] = args; - const cmdOptions = { key: key }; - result = await executeShellCommandOnServer(cmd, cmdOptions); - setOutput((prevOutput) => [...prevOutput, newOutput, result]); - } catch (error: unknown) { - console.error('Error executing command:', error); - result = 'Error executing command'; - return `Error: ${String(error)}`; - } - break; - - case 'SET': - if (args.length === 2) { - const [key, value] = args; - try { - const cmdOptions = { key: key, value: value }; - result = await executeShellCommandOnServer(cmd, cmdOptions); - setOutput((prevOutput) => [...prevOutput, newOutput, result]); - } catch (error: unknown) { - console.error('Error executing command:', error); - result = 'Error executing command'; - setOutput((prevOutput) => [...prevOutput, newOutput, result]); - return `Error: ${String((error as Error).message || error)}`; - } - } else { - result = 'Invalid command. Usage: SET key value'; - setOutput((prevOutput) => [...prevOutput, newOutput, result]); - } - break; - - case 'DEL': - if (args.length <= 1) { - const [keys] = args; - try { - const cmdOptions = { keys: [keys] }; - result = await executeShellCommandOnServer(cmd, cmdOptions); - setOutput((prevOutput) => [...prevOutput, newOutput, result]); - } catch (error: unknown) { - console.error('Error executing command:', error); - result = 'Error executing command'; - setOutput((prevOutput) => [...prevOutput, newOutput, result]); - return `Error: ${String((error as Error).message || error)}`; - } - } else { - result = 'Invalid command. Usage: DEL key1 key2 ....'; - setOutput((prevOutput) => [...prevOutput, newOutput, result]); - } - break; - - default: - result = `Unknown command: ${cmd}`; - setOutput((prevOutput) => [...prevOutput, newOutput, result]); - } -}; diff --git a/apps/playground-web/shared/utils/commonUtils.ts b/apps/playground-web/shared/utils/commonUtils.ts index c07147e..beaa013 100644 --- a/apps/playground-web/shared/utils/commonUtils.ts +++ b/apps/playground-web/shared/utils/commonUtils.ts @@ -1,5 +1,41 @@ +import { MonoResponse } from '@/lib/monoResponse'; + export const formatTime = (seconds: number): string => { const minutes = Math.floor(seconds / 60); const remainingSeconds = seconds % 60; return `${minutes}:${remainingSeconds < 10 ? '0' : ''}${remainingSeconds}`; }; + +interface HandleResultProps { + result: { + headers: { [key: string]: string }; // Correctly defined headers type + body: MonoResponse; // MonoResponse for body + }; + newOutput: string; + setOutput: (prevOutput: any) => any; // Adjust type as necessary + onCommandExecuted: (commandsLeft: number) => void; // Allow undefined +} + +export const handleResult = ({ + result, + newOutput, + setOutput, + onCommandExecuted, +}: HandleResultProps) => { + if (result?.body?.data) { + setOutput((prevOutput: any) => [ + ...prevOutput, + newOutput, + result?.body?.data, + ]); + } else if (result?.body?.error) { + setOutput((prevOutput: any) => [ + ...prevOutput, + newOutput, + result?.body?.error, + ]); + } + + const commandsLeft = Number(result.headers['x-ratelimit-remaining']); + onCommandExecuted(commandsLeft); +}; diff --git a/apps/playground-web/shared/utils/shellUtils.ts b/apps/playground-web/shared/utils/shellUtils.ts index b8f6dd8..269f94e 100644 --- a/apps/playground-web/shared/utils/shellUtils.ts +++ b/apps/playground-web/shared/utils/shellUtils.ts @@ -2,10 +2,15 @@ import { executeShellCommandOnServer } from '@/lib/api'; import { CommandHandler } from '@/types'; +import { handleResult } from '@/shared/utils/commonUtils'; -export const handleCommand = async ({ command, setOutput }: CommandHandler) => { +export const handleCommand = async ({ + command, + setOutput, + onCommandExecuted, +}: CommandHandler) => { const newOutput = `dice > ${command}`; - let result: string; + let result: any; const { cmd, args } = parseCommand(command); @@ -14,7 +19,7 @@ export const handleCommand = async ({ command, setOutput }: CommandHandler) => { } try { result = await executeShellCommandOnServer(cmd, args); - setOutput((prevOutput) => [...prevOutput, newOutput, result]); + handleResult({ result, newOutput, setOutput, onCommandExecuted }); } catch (error: unknown) { console.error('Error executing command:', error); result = 'Error executing command'; diff --git a/apps/playground-web/types/index.d.ts b/apps/playground-web/types/index.d.ts index 1324c2e..cd03372 100644 --- a/apps/playground-web/types/index.d.ts +++ b/apps/playground-web/types/index.d.ts @@ -3,4 +3,5 @@ import * as React from 'react'; export interface CommandHandler { command: string; setOutput: React.Dispatch>; + onCommandExecuted: (commandsLeft: number) => void; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 44f384b..adb50a8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -108,6 +108,9 @@ importers: sharp: specifier: ^0.33.5 version: 0.33.5 + zod: + specifier: ^3.23.8 + version: 3.23.8 devDependencies: '@dicedb/eslint-config': specifier: workspace:* @@ -223,7 +226,7 @@ importers: version: 29.7.0 ts-jest: specifier: ^29.2.5 - version: 29.2.5(@babel/core@7.25.8)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.8))(jest@29.7.0(@types/node@20.16.11)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.16.11)(typescript@5.6.3)))(typescript@5.6.3) + version: 29.2.5(@babel/core@7.25.8)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.8))(jest@29.7.0(@types/node@20.16.11)(babel-plugin-macros@3.1.0))(typescript@5.6.3) typescript: specifier: ^5.3.3 version: 5.6.3 @@ -238,7 +241,7 @@ importers: version: 7.18.0(eslint@8.57.1)(typescript@5.6.3) '@vercel/style-guide': specifier: ^5.2.0 - version: 5.2.0(@next/eslint-plugin-next@14.2.15)(eslint@8.57.1)(jest@29.7.0(@types/node@20.16.11)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.16.11)(typescript@5.6.3)))(prettier@3.3.3)(typescript@5.6.3) + version: 5.2.0(@next/eslint-plugin-next@14.2.15)(eslint@8.57.1)(jest@29.7.0(babel-plugin-macros@3.1.0))(prettier@3.3.3)(typescript@5.6.3) eslint-config-prettier: specifier: ^9.1.0 version: 9.1.0(eslint@8.57.1) @@ -4624,6 +4627,9 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + zod@3.23.8: + resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} + snapshots: '@adobe/css-tools@4.4.0': {} @@ -5906,7 +5912,7 @@ snapshots: '@ungap/structured-clone@1.2.0': {} - '@vercel/style-guide@5.2.0(@next/eslint-plugin-next@14.2.15)(eslint@8.57.1)(jest@29.7.0(@types/node@20.16.11)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.16.11)(typescript@5.6.3)))(prettier@3.3.3)(typescript@5.6.3)': + '@vercel/style-guide@5.2.0(@next/eslint-plugin-next@14.2.15)(eslint@8.57.1)(jest@29.7.0(babel-plugin-macros@3.1.0))(prettier@3.3.3)(typescript@5.6.3)': dependencies: '@babel/core': 7.25.8 '@babel/eslint-parser': 7.25.8(@babel/core@7.25.8)(eslint@8.57.1) @@ -5914,13 +5920,13 @@ snapshots: '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3) '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.6.3) eslint-config-prettier: 9.1.0(eslint@8.57.1) - eslint-import-resolver-alias: 1.1.2(eslint-plugin-import@2.31.0) + eslint-import-resolver-alias: 1.1.2(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1)) eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-plugin-import@2.31.0)(eslint@8.57.1) eslint-plugin-eslint-comments: 3.2.0(eslint@8.57.1) eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) - eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(jest@29.7.0(@types/node@20.16.11)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.16.11)(typescript@5.6.3)))(typescript@5.6.3) + eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(jest@29.7.0(babel-plugin-macros@3.1.0))(typescript@5.6.3) eslint-plugin-jsx-a11y: 6.10.0(eslint@8.57.1) - eslint-plugin-playwright: 0.16.0(eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(jest@29.7.0(@types/node@20.16.11)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.16.11)(typescript@5.6.3)))(typescript@5.6.3))(eslint@8.57.1) + eslint-plugin-playwright: 0.16.0(eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(jest@29.7.0(babel-plugin-macros@3.1.0))(typescript@5.6.3))(eslint@8.57.1) eslint-plugin-react: 7.37.1(eslint@8.57.1) eslint-plugin-react-hooks: 4.6.2(eslint@8.57.1) eslint-plugin-testing-library: 6.3.0(eslint@8.57.1)(typescript@5.6.3) @@ -6844,7 +6850,7 @@ snapshots: eslint: 8.57.1 eslint-plugin-turbo: 2.1.3(eslint@8.57.1) - eslint-import-resolver-alias@1.1.2(eslint-plugin-import@2.31.0): + eslint-import-resolver-alias@1.1.2(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1)): dependencies: eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) @@ -6862,7 +6868,7 @@ snapshots: debug: 4.3.7 enhanced-resolve: 5.17.1 eslint: 8.57.1 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1) fast-glob: 3.3.2 get-tsconfig: 4.8.1 is-bun-module: 1.2.1 @@ -6881,7 +6887,7 @@ snapshots: debug: 4.3.7 enhanced-resolve: 5.17.1 eslint: 8.57.1 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1) fast-glob: 3.3.2 get-tsconfig: 4.8.1 is-bun-module: 1.2.1 @@ -6894,7 +6900,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): + eslint-module-utils@2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: @@ -6905,7 +6911,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): + eslint-module-utils@2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: @@ -6933,7 +6939,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -6951,7 +6957,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(jest@29.7.0(@types/node@20.16.11)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.16.11)(typescript@5.6.3)))(typescript@5.6.3): + eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(jest@29.7.0(babel-plugin-macros@3.1.0))(typescript@5.6.3): dependencies: '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@5.6.3) eslint: 8.57.1 @@ -6984,11 +6990,11 @@ snapshots: eslint-plugin-only-warn@1.1.0: {} - eslint-plugin-playwright@0.16.0(eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(jest@29.7.0(@types/node@20.16.11)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.16.11)(typescript@5.6.3)))(typescript@5.6.3))(eslint@8.57.1): + eslint-plugin-playwright@0.16.0(eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(jest@29.7.0(babel-plugin-macros@3.1.0))(typescript@5.6.3))(eslint@8.57.1): dependencies: eslint: 8.57.1 optionalDependencies: - eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(jest@29.7.0(@types/node@20.16.11)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.16.11)(typescript@5.6.3)))(typescript@5.6.3) + eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(jest@29.7.0(babel-plugin-macros@3.1.0))(typescript@5.6.3) eslint-plugin-react-hooks@4.6.2(eslint@8.57.1): dependencies: @@ -9554,7 +9560,7 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-jest@29.2.5(@babel/core@7.25.8)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.8))(jest@29.7.0(@types/node@20.16.11)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.16.11)(typescript@5.6.3)))(typescript@5.6.3): + ts-jest@29.2.5(@babel/core@7.25.8)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.8))(jest@29.7.0(@types/node@20.16.11)(babel-plugin-macros@3.1.0))(typescript@5.6.3): dependencies: bs-logger: 0.2.6 ejs: 3.1.10 @@ -9893,3 +9899,5 @@ snapshots: yn@3.1.1: {} yocto-queue@0.1.0: {} + + zod@3.23.8: {} diff --git a/src/lib/__tests__/api.test.ts b/src/lib/__tests__/api.test.ts index 5bd6590..a6d91f0 100644 --- a/src/lib/__tests__/api.test.ts +++ b/src/lib/__tests__/api.test.ts @@ -1,5 +1,6 @@ import { executeShellCommandOnServer } from '../api'; import { WebService } from '@/services/webServices'; +import { handleCommand } from '@/shared/utils/cliUtils'; // Mock WebService jest.mock('@/services/webServices', () => ({ @@ -90,4 +91,26 @@ describe('executeShellCommandOnServer', () => { ); expect(result).toEqual('Some Response'); }); + + it('should call onCommandExecuted with (1000) for GET command', async () => { + const command = 'GET testKey'; + const mockResult = { + body: { data: 'mockData' }, + headers: { 'x-ratelimit-remaining': 1000 }, + }; // Updated mock result + (executeShellCommandOnServer as jest.Mock).mockResolvedValueOnce( + mockResult, + ); // Mock the API response + + const setOutputMock = jest.fn(); + const onCommandExecutedMock = jest.fn(); + + await handleCommand({ + command, + setOutput: setOutputMock, + onCommandExecuted: onCommandExecutedMock, + }); + + expect(onCommandExecutedMock).toHaveBeenCalledWith(1000); + }); });