diff --git a/packages/superofficedx-vscode-core/package.json b/packages/superofficedx-vscode-core/package.json index 95fef24..6e61ad8 100644 --- a/packages/superofficedx-vscode-core/package.json +++ b/packages/superofficedx-vscode-core/package.json @@ -78,6 +78,11 @@ "id": "superOfficeDX.scriptExplorer", "name": "Scripts", "icon": "/resources/logo.svg" + }, + { + "id": "superOfficeDX.extraTablesExplorer", + "name": "Extra Tables Explorer", + "icon": "/resources/logo.svg" } ] }, diff --git a/packages/superofficedx-vscode-core/src/commands/handlers/scriptCommands.ts b/packages/superofficedx-vscode-core/src/commands/handlers/scriptCommands.ts index f882cd9..f086325 100644 --- a/packages/superofficedx-vscode-core/src/commands/handlers/scriptCommands.ts +++ b/packages/superofficedx-vscode-core/src/commands/handlers/scriptCommands.ts @@ -6,7 +6,7 @@ import { Node } from '../../providers/treeViewDataProvider'; import { SuperofficeAuthenticationProvider } from '../../providers/superofficeAuthenticationProvider'; import { IHttpService } from '../../services/httpService'; import { INodeService } from '../../services/nodeService'; -import { ScriptInfo } from '../../types/script'; +import { ScriptInfo } from '../../types/odata/script'; import { IScriptCommands, diff --git a/packages/superofficedx-vscode-core/src/commands/types/commandContracts.ts b/packages/superofficedx-vscode-core/src/commands/types/commandContracts.ts index 0dc8463..82b5703 100644 --- a/packages/superofficedx-vscode-core/src/commands/types/commandContracts.ts +++ b/packages/superofficedx-vscode-core/src/commands/types/commandContracts.ts @@ -3,7 +3,7 @@ import { Node } from '../../providers/treeViewDataProvider'; import { SuperofficeAuthenticationProvider } from '../../providers/superofficeAuthenticationProvider'; import { IHttpService } from '../../services/httpService'; import { INodeService } from '../../services/nodeService'; -import { ScriptInfo } from '../../types/script'; +import { ScriptInfo } from '../../types/odata/script'; import { SuperOfficeAuthenticationSession } from '../../types/authSession'; /** diff --git a/packages/superofficedx-vscode-core/src/container/configurations/configurationKeys.ts b/packages/superofficedx-vscode-core/src/container/configurations/configurationKeys.ts index 92e4c42..a6ae75d 100644 --- a/packages/superofficedx-vscode-core/src/container/configurations/configurationKeys.ts +++ b/packages/superofficedx-vscode-core/src/container/configurations/configurationKeys.ts @@ -15,6 +15,7 @@ export const ConfigurationKeys = { // Providers AuthenticationProvider: Symbol('AuthenticationProvider'), TreeViewDataProvider: Symbol('TreeViewDataProvider'), + ExtraTablesTreeViewDataProvider: Symbol('ExtraTablesTreeViewDataProvider'), TextDocumentContentProvider: Symbol('TextDocumentContentProvider'), // VS Code Context diff --git a/packages/superofficedx-vscode-core/src/container/configurations/providerConfiguration.ts b/packages/superofficedx-vscode-core/src/container/configurations/providerConfiguration.ts index c693d71..f6ee722 100644 --- a/packages/superofficedx-vscode-core/src/container/configurations/providerConfiguration.ts +++ b/packages/superofficedx-vscode-core/src/container/configurations/providerConfiguration.ts @@ -2,6 +2,7 @@ import { CustomTextDocumentContentProvider } from "../../providers/textDocumentContentProvider"; import { SuperofficeAuthenticationProvider } from "../../providers/superofficeAuthenticationProvider"; import { TreeViewDataProvider } from "../../providers/treeViewDataProvider"; +import { ExtraTablesTreeViewDataProvider } from "../../providers/extraTablesTreeViewDataProvider"; import { DIContainer } from "../core/diContainer"; import { ConfigurationKeys } from "./configurationKeys"; @@ -26,6 +27,14 @@ export function configureProviders(container: DIContainer): void { ) ); + container.registerSingleton(ConfigurationKeys.ExtraTablesTreeViewDataProvider, () => + new ExtraTablesTreeViewDataProvider( + container.resolve(ConfigurationKeys.ExtensionContext), + container.resolve(ConfigurationKeys.AuthenticationProvider), + container.resolve(ConfigurationKeys.HttpService) + ) + ); + container.registerSingleton(ConfigurationKeys.TextDocumentContentProvider, () => new CustomTextDocumentContentProvider() ); diff --git a/packages/superofficedx-vscode-core/src/extension.ts b/packages/superofficedx-vscode-core/src/extension.ts index 255809d..9bdd1df 100644 --- a/packages/superofficedx-vscode-core/src/extension.ts +++ b/packages/superofficedx-vscode-core/src/extension.ts @@ -2,6 +2,7 @@ import { ExtensionContext, workspace, window } from 'vscode'; import { CustomTextDocumentContentProvider } from "./providers/textDocumentContentProvider"; import { SuperofficeAuthenticationProvider } from "./providers/superofficeAuthenticationProvider"; import { TreeViewDataProvider } from "./providers/treeViewDataProvider"; +import { ExtraTablesTreeViewDataProvider } from "./providers/extraTablesTreeViewDataProvider"; import { registerCommands } from './commands/commandRegistration'; import { getCustomScheme } from './utils'; import { createContainer } from './container/containerRegistration'; @@ -17,17 +18,21 @@ export async function activate(context: ExtensionContext): Promise { const textContentProvider = container.resolve(ConfigurationKeys.TextDocumentContentProvider); const authProvider = container.resolve(ConfigurationKeys.AuthenticationProvider); const treeViewDataProvider = container.resolve(ConfigurationKeys.TreeViewDataProvider); + const extraTablesTreeViewDataProvider = container.resolve(ConfigurationKeys.ExtraTablesTreeViewDataProvider); // Register providers with VS Code context.subscriptions.push(workspace.registerTextDocumentContentProvider(getCustomScheme(), textContentProvider)); context.subscriptions.push(authProvider); const treeviewProvider = window.registerTreeDataProvider(TreeViewDataProvider.viewId, treeViewDataProvider); + const extraTablesTreeviewProvider = window.registerTreeDataProvider(ExtraTablesTreeViewDataProvider.viewId, extraTablesTreeViewDataProvider); context.subscriptions.push(treeviewProvider); + context.subscriptions.push(extraTablesTreeviewProvider); // Listen for authentication session changes to refresh the tree view authProvider.onDidChangeSessions(() => { treeViewDataProvider.refresh(); + extraTablesTreeViewDataProvider.refresh(); }); // Register commands diff --git a/packages/superofficedx-vscode-core/src/handlers/httpHandler.ts b/packages/superofficedx-vscode-core/src/handlers/httpHandler.ts index 82c698a..8225121 100644 --- a/packages/superofficedx-vscode-core/src/handlers/httpHandler.ts +++ b/packages/superofficedx-vscode-core/src/handlers/httpHandler.ts @@ -35,47 +35,117 @@ export interface IHttpHandler { } export class HttpHandler implements IHttpHandler { - private async request(method: string, url: string, body?: unknown, headers: Record = {}): Promise { + /** + * Internal method to make HTTP requests + * @param method - HTTP method (GET, POST, PUT, DELETE, PATCH) + * @param url - Request URL + * @param body - Request body (optional) + * @param options - Request options including headers and timeout + * @returns Promise resolving to the response data + */ + private async request(method: string, url: string, body?: unknown, options: HttpRequestOptions = {}): Promise { + const { headers = {}, timeout = 30000 } = options; try { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), timeout); + + const requestHeaders: Record = { ...headers }; + + // Only set Content-Type for requests with body + if (body !== undefined) { + requestHeaders['Content-Type'] = 'application/json'; + } + const response = await fetch(url, { method, - headers: { - 'Content-Type': 'application/json', - ...headers, - }, + headers: requestHeaders, body: body ? JSON.stringify(body) : undefined, + signal: controller.signal, }); + clearTimeout(timeoutId); + if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); + throw new HttpError( + `HTTP ${response.status} ${response.statusText}`, + response.status, + url, + method, + response + ); } return response.json() as Promise; } catch (error) { - throw new Error('Error sending request: ' + error); - } + if (error instanceof HttpError) { + throw error; + } + if (error instanceof Error && error.name === 'AbortError') { + throw new HttpError(`Request timeout after ${timeout}ms`, undefined, url, method); + } + + throw new HttpError( + `Request failed: ${error instanceof Error ? error.message : String(error)}`, + undefined, + url, + method + ); + } } - public async get(url: string, headers: Record = {}): Promise { - return await this.request('GET', url, undefined, headers); + /** + * Make a GET request + * @param url - Request URL + * @param options - Request options including headers and timeout + * @returns Promise resolving to the response data + */ + public async get(url: string, options?: HttpRequestOptions): Promise { + return await this.request('GET', url, undefined, options); } - public async post(url: string, body: unknown, headers: Record = {}): Promise { - return await this.request('POST', url, body, headers); + /** + * Make a POST request + * @param url - Request URL + * @param body - Request body + * @param options - Request options including headers and timeout + * @returns Promise resolving to the response data + */ + public async post(url: string, body: unknown, options?: HttpRequestOptions): Promise { + return await this.request('POST', url, body, options); } - public async put(url: string, body: unknown, headers: Record = {}): Promise { - return await this.request('PUT', url, body, headers); + /** + * Make a PUT request + * @param url - Request URL + * @param body - Request body + * @param options - Request options including headers and timeout + * @returns Promise resolving to the response data + */ + public async put(url: string, body: unknown, options?: HttpRequestOptions): Promise { + return await this.request('PUT', url, body, options); } - public async delete(url: string, headers: Record = {}): Promise { - return await this.request('DELETE', url, undefined, headers); + /** + * Make a DELETE request + * @param url - Request URL + * @param options - Request options including headers and timeout + * @returns Promise resolving to the response data + */ + public async delete(url: string, options?: HttpRequestOptions): Promise { + return await this.request('DELETE', url, undefined, options); } - public async patch(url: string, body: unknown, headers: Record = {}): Promise { - return await this.request('PATCH', url, body, headers); + /** + * Make a PATCH request + * @param url - Request URL + * @param body - Request body + * @param options - Request options including headers and timeout + * @returns Promise resolving to the response data + */ + public async patch(url: string, body: unknown, options?: HttpRequestOptions): Promise { + return await this.request('PATCH', url, body, options); } } diff --git a/packages/superofficedx-vscode-core/src/providers/extraTablesTreeViewDataProvider.ts b/packages/superofficedx-vscode-core/src/providers/extraTablesTreeViewDataProvider.ts new file mode 100644 index 0000000..8981f0a --- /dev/null +++ b/packages/superofficedx-vscode-core/src/providers/extraTablesTreeViewDataProvider.ts @@ -0,0 +1,107 @@ +import { TreeItem, TreeItemCollapsibleState, ThemeIcon, TreeDataProvider, EventEmitter, Event, ExtensionContext } from 'vscode'; +import { SuperofficeAuthenticationProvider } from './superofficeAuthenticationProvider'; +import { IHttpService } from '../services/httpService'; +import { ExtraTable } from '../types/odata/extratable'; + +export class ExtraTablesNode implements TreeItem { + contextValue: string; + collapsibleState: TreeItemCollapsibleState; + + constructor( + public readonly label: string, + public readonly children?: ExtraTablesNode[], + public readonly iconPath?: ThemeIcon + ) { + this.contextValue = children && children.length > 0 ? 'extraTablesParent' : 'extraTablesChild'; + this.collapsibleState = children && children.length > 0 + ? TreeItemCollapsibleState.Collapsed + : TreeItemCollapsibleState.None; + } +} + +export class ExtraTablesTreeViewDataProvider implements TreeDataProvider { + private _onDidChangeTreeData: EventEmitter = new EventEmitter(); + readonly onDidChangeTreeData: Event = this._onDidChangeTreeData.event; + + public static readonly viewId = 'superOfficeDX.extraTablesExplorer'; + + constructor( + _context: ExtensionContext, // Parameter required for DI container compatibility + private authProvider: SuperofficeAuthenticationProvider, + private httpService: IHttpService) { + // _context is intentionally unused; required for DI container compatibility + } + + // Call this method to trigger a refresh of the tree view + public refresh(): void { + this._onDidChangeTreeData.fire(undefined); + } + + getTreeItem(element: ExtraTablesNode): TreeItem { + return element; + } + + async getChildren(element?: ExtraTablesNode): Promise { + if (element) { + return element.children || []; + } + + const currentSession = this.authProvider.getCurrentSession(); + if (currentSession) { + try { + const scriptResponseData = await this.httpService.getExtraTables(currentSession); + + // Group scripResponseData by property "extra_tables.table_name" + const groupedTables = new Map(); + scriptResponseData.value.forEach(item => { + const tableName = item["extra_tables.table_name"]; + if (!groupedTables.has(tableName)) { + groupedTables.set(tableName, []); + } + groupedTables.get(tableName)!.push(item); + }); + + const tableNodes: ExtraTablesNode[] = []; + groupedTables.forEach((tableItems, tableName) => { + const fieldNodes = tableItems.map(item => { + const typeNode = new ExtraTablesNode( + "type: " + item["extra_tables.(extra_fields->extra_table).type"], + undefined, + new ThemeIcon('symbol-property') + ); + + const descriptionNode = new ExtraTablesNode( + "description: " + item["extra_tables.(extra_fields->extra_table).description"], + undefined, + new ThemeIcon('symbol-property') + ); + + return new ExtraTablesNode( + item["extra_tables.(extra_fields->extra_table).field_name"], + [typeNode, descriptionNode], + new ThemeIcon('symbol-field') + ); + }); + + // Create parent node for the table + const tableNode = new ExtraTablesNode( + tableName, + fieldNodes, + new ThemeIcon('database') + ); + + tableNodes.push(tableNode); + }); + + return tableNodes; + } catch (err) { + if (err instanceof Error) { + throw new Error(err.message); + } else { + throw new Error(String(err)); + } + } + } + return []; + } +} diff --git a/packages/superofficedx-vscode-core/src/providers/treeViewDataProvider.ts b/packages/superofficedx-vscode-core/src/providers/treeViewDataProvider.ts index 4244bd7..b2e75a1 100644 --- a/packages/superofficedx-vscode-core/src/providers/treeViewDataProvider.ts +++ b/packages/superofficedx-vscode-core/src/providers/treeViewDataProvider.ts @@ -1,5 +1,5 @@ import { Uri, TreeItem, TreeItemCollapsibleState, ThemeIcon, Command, TreeDataProvider, EventEmitter, Event, ExtensionContext } from 'vscode'; -import { ScriptInfo } from '../types/script'; +import { ScriptInfo } from '../types/odata/script'; import { SuperofficeAuthenticationProvider } from './superofficeAuthenticationProvider'; import { IHttpService } from '../services/httpService'; diff --git a/packages/superofficedx-vscode-core/src/services/fileSystemService.ts b/packages/superofficedx-vscode-core/src/services/fileSystemService.ts index a2e6c94..70fa04b 100644 --- a/packages/superofficedx-vscode-core/src/services/fileSystemService.ts +++ b/packages/superofficedx-vscode-core/src/services/fileSystemService.ts @@ -1,7 +1,7 @@ import { FileType, Uri, workspace, window, FileSystemError } from 'vscode' import { IFileSystemHandler } from '../handlers/fileSystemHandler'; -import { ScriptEntity } from '../types/script'; +import { ScriptEntity } from '../types/odata/script'; import { SuoFile } from '../types/suoFile'; import path from 'path'; import { getFileType } from '../utils'; diff --git a/packages/superofficedx-vscode-core/src/services/httpService.ts b/packages/superofficedx-vscode-core/src/services/httpService.ts index 4e8e463..742c57c 100644 --- a/packages/superofficedx-vscode-core/src/services/httpService.ts +++ b/packages/superofficedx-vscode-core/src/services/httpService.ts @@ -1,11 +1,12 @@ import { Uri } from "vscode"; import { IHttpHandler } from "../handlers/httpHandler"; //import { DynamicScriptOdata, ExecuteScriptResponse, Hierarchy, ScriptEntity, Scripts, State, SuperOfficeAuthenticationSession, UserClaims } from "../types/index"; -import { DynamicScriptOdata } from "../types/script"; -import { ExecuteScriptResponse } from "../types/script"; +import { DynamicScriptOdata } from "../types/odata/script"; +import { ExecuteScriptResponse } from "../types/odata/script"; import { Hierarchy } from "../types/hierarchy"; -import { ScriptEntity } from "../types/script"; -import { Scripts } from "../types/script"; +import { ScriptEntity } from "../types/odata/script"; +import { Scripts } from "../types/odata/script"; +import { ExtraTablesOdata } from "../types/odata/extratable"; import { State } from "../types/state"; import { SuperOfficeAuthenticationSession } from "../types/authSession"; import { UserClaims } from "../types/userClaims"; @@ -14,6 +15,7 @@ import { IFileSystemService } from "./fileSystemService"; export interface IHttpService { getTenantState(claims: UserClaims): Promise; getScriptList(session: SuperOfficeAuthenticationSession): Promise; + getExtraTables(session: SuperOfficeAuthenticationSession): Promise; getCrmScriptEntity(session: SuperOfficeAuthenticationSession, ejscriptId: number): Promise; executeScript(session: SuperOfficeAuthenticationSession, script: string): Promise; downloadScript(session: SuperOfficeAuthenticationSession, ejscriptId: number): Promise; @@ -50,6 +52,10 @@ export class HttpService implements IHttpService { return `/v1/archive/dynamic?$select=ejscript.id,ejscript.type,ejscript.description,ejscript.include_id,ejscript.access_key,ejscript.unique_identifier&$filter=ejscript.id eq '${ejscriptId}'`; } + get getExtraTablesUrl(): string { + return `/v1/archive/dynamic?$select=extra_tables.id,extra_tables.table_name,extra_tables.(extra_fields->extra_table).field_name,extra_tables.(extra_fields->extra_table).type,extra_tables.(extra_fields->extra_table).description`; + } + // private getCrmScriptByEjscriptId(ejscriptId: number) { @@ -89,6 +95,22 @@ export class HttpService implements IHttpService { } } + public async getExtraTables(session: SuperOfficeAuthenticationSession): Promise { + try { + return await this.httpHandler.get(`${session.webApiUri}${this.getExtraTablesUrl}`, + { + headers: { + Authorization: `Bearer ${session.accessToken}`, + 'Accept': 'application/json' + } + } + ); + } + catch (error) { + throw new Error('Error getting ExtraTables: ' + error); + } + }; + public async getCrmScriptEntity(session: SuperOfficeAuthenticationSession, ejscriptId: number): Promise { try { return await this.httpHandler.get(`${session.webApiUri}${this.getCrmScriptUri}/${ejscriptId}`, diff --git a/packages/superofficedx-vscode-core/src/types/odata/extratable.ts b/packages/superofficedx-vscode-core/src/types/odata/extratable.ts new file mode 100644 index 0000000..853f33b --- /dev/null +++ b/packages/superofficedx-vscode-core/src/types/odata/extratable.ts @@ -0,0 +1,15 @@ +import { ResponseOdataMetadata } from "./odataResponse"; + +export interface ExtraTable { + PrimaryKey: string; + EntityName: string; + "extra_tables.(extra_fields->extra_table).field_name": string; + "extra_tables.id": number; + "extra_tables.table_name": string; + "extra_tables.(extra_fields->extra_table).type": string; + "extra_tables.(extra_fields->extra_table).description": string; +} + +export interface ExtraTablesOdata extends ResponseOdataMetadata { + value: ExtraTable[]; +} diff --git a/packages/superofficedx-vscode-core/src/types/odata/odataResponse.ts b/packages/superofficedx-vscode-core/src/types/odata/odataResponse.ts new file mode 100644 index 0000000..75cd55d --- /dev/null +++ b/packages/superofficedx-vscode-core/src/types/odata/odataResponse.ts @@ -0,0 +1,4 @@ +export interface ResponseOdataMetadata { + "odata.metadata": string; + "odata.nextLink": null | string; // Assuming the value can sometimes be a string +} diff --git a/packages/superofficedx-vscode-core/src/types/script.ts b/packages/superofficedx-vscode-core/src/types/odata/script.ts similarity index 91% rename from packages/superofficedx-vscode-core/src/types/script.ts rename to packages/superofficedx-vscode-core/src/types/odata/script.ts index c52d2ba..52d023e 100644 --- a/packages/superofficedx-vscode-core/src/types/script.ts +++ b/packages/superofficedx-vscode-core/src/types/odata/script.ts @@ -1,7 +1,4 @@ -interface ResponseOdataMetadata { - "odata.metadata": string; - "odata.nextLink": null | string; // Assuming the value can sometimes be a string -} +import { ResponseOdataMetadata } from "./odataResponse"; export interface Scripts extends ResponseOdataMetadata { value: ScriptInfo[]; @@ -25,7 +22,7 @@ export interface ScriptInfo { uniqueIdentifier: string; type: number; registeredBy: string; - registeredDate: Date; + registeredDate: Date; updatedBy: string; updatedDate: Date; path: string; @@ -38,7 +35,7 @@ export interface DynamicScriptOdata { "odata.metadata": string; "odata.nextLink": null | string; value: DynamicScriptOdataValue[]; - + } interface DynamicScriptOdataValue { PrimaryKey: string; @@ -67,7 +64,7 @@ export interface ScriptEntity { ValidationResult: ValidationResult; } -interface ValidationResult{ +interface ValidationResult { Valid: boolean; ErrorMessage: string; LineNumber: number; @@ -99,4 +96,4 @@ export interface ExecuteScriptResponse { FieldLength: number; } }; -} \ No newline at end of file +} diff --git a/packages/superofficedx-vscode-core/src/utils.ts b/packages/superofficedx-vscode-core/src/utils.ts index 6c9a1d1..79b8e4b 100644 --- a/packages/superofficedx-vscode-core/src/utils.ts +++ b/packages/superofficedx-vscode-core/src/utils.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { Disposable, Event, EventEmitter, ExtensionContext } from "vscode"; -import { ScriptEntity } from "./types/script"; +import { ScriptEntity } from "./types/odata/script"; export interface PromiseAdapter { ( diff --git a/packages/superofficedx-vscode-core/tests/suite/commands/registerCommands.test.ts b/packages/superofficedx-vscode-core/tests/suite/commands/registerCommands.test.ts new file mode 100644 index 0000000..84917dd --- /dev/null +++ b/packages/superofficedx-vscode-core/tests/suite/commands/registerCommands.test.ts @@ -0,0 +1,116 @@ +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; +import * as vscode from 'vscode'; +import { registerCommands } from '../../../src/commands/commandRegistration'; +import { CommandKeys } from '../../../src/commands/commandKeys'; +import { IHttpService } from '../../../src/services/httpService'; +import { INodeService } from '../../../src/services/nodeService'; +import { SuperofficeAuthenticationProvider } from '../../../src/providers/superofficeAuthenticationProvider'; +import { DIContainer } from '../../../src/container/core/diContainer'; +import { ConfigurationKeys } from '../../../src/container/configurations/configurationKeys'; +import { ScriptEntity } from '../../../src/types/odata/script'; + +/** + * Creates a test-specific container factory. + * This is often cleaner than mocking the container. + */ +function createTestContainer( + context: vscode.ExtensionContext, + mockHttpService: IHttpService, + mockNodeService: INodeService, + mockAuthProvider: SuperofficeAuthenticationProvider +): DIContainer { + const container = new DIContainer(); + + // Register the mocked services + container.registerSingleton(ConfigurationKeys.ExtensionContext, () => context); + container.registerSingleton(ConfigurationKeys.HttpService, () => mockHttpService); + container.registerSingleton(ConfigurationKeys.NodeService, () => mockNodeService); + container.registerSingleton(ConfigurationKeys.AuthenticationProvider, () => mockAuthProvider); + + // Register other services as needed (auth provider, etc.) + // You might need to add more based on what your commands require + + return container; +} + +describe('registerCommands - Alternative Approach', () => { + let context: vscode.ExtensionContext; + let mockHttpService: IHttpService; + let mockNodeService: INodeService; + let mockAuthProvider: SuperofficeAuthenticationProvider; + + // Get all command values from CommandKeys for dynamic testing + const expectedCommands = Object.values(CommandKeys); + + beforeEach(() => { + // Mock VSCode extension context + context = { + subscriptions: [], + } as unknown as vscode.ExtensionContext; + + // Create mock services using vitest + mockHttpService = { + getTenantState: vi.fn(), + getCrmScriptEntity: vi.fn(), + getCrmScriptByUniqueIdentifier: vi.fn(), + getScriptsInFolder: vi.fn(), + executeScript: vi.fn(), + validateScript: vi.fn(), + saveScript: vi.fn(), + } as unknown as IHttpService; + + mockNodeService = { + getChildren: vi.fn(), + getParent: vi.fn(), + getNodeById: vi.fn(), + } as unknown as INodeService; + + // Create mock authentication provider + mockAuthProvider = { + onDidChangeSessions: vi.fn(), + getSessions: vi.fn().mockResolvedValue([]), + createSession: vi.fn(), + removeSession: vi.fn(), + dispose: vi.fn(), + } as unknown as SuperofficeAuthenticationProvider; + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it('should register commands using test container', () => { + // Create test container with mocked dependencies + const testContainer = createTestContainer(context, mockHttpService, mockNodeService, mockAuthProvider); + + // Call registerCommands + registerCommands(testContainer); + + // Check that the number of disposables matches the number of defined commands + expect(context.subscriptions.length).toBe(expectedCommands.length); + + // Check that each subscription is a valid disposable + context.subscriptions.forEach((sub) => { + expect(typeof sub.dispose).toBe('function'); + }); + }); + + it('should handle command execution with mocked services', () => { + // This approach allows you to test actual command behavior with mocked dependencies + const testContainer = createTestContainer(context, mockHttpService, mockNodeService, mockAuthProvider); + + // Set up mock responses + vi.mocked(mockHttpService.getCrmScriptEntity).mockResolvedValue({ + SourceCode: 'test script content', + Name: 'Test Script' + } as ScriptEntity); + + registerCommands(testContainer); + + // You can now test that the commands were registered correctly + expect(context.subscriptions.length).toBeGreaterThan(0); + + // You could even test command execution here if needed + // (though that might be better suited for integration tests) + }); +}); diff --git a/packages/superofficedx-vscode-core/tests/unit/vitest/handlers/httpHandler.test.ts b/packages/superofficedx-vscode-core/tests/unit/vitest/handlers/httpHandler.test.ts index 6bf027c..d1aa696 100644 --- a/packages/superofficedx-vscode-core/tests/unit/vitest/handlers/httpHandler.test.ts +++ b/packages/superofficedx-vscode-core/tests/unit/vitest/handlers/httpHandler.test.ts @@ -77,6 +77,6 @@ describe('HttpHandler Test Suite', () => { await expect(httpHandler.get('https://api.example.com/data')) .rejects - .toThrow(/HTTP error! status: 500/); + .toThrow(/HTTP 500/); }); });