',
+ ].join('');
+ mockFetch.mockResolvedValue({ text: async () => html });
+ const result = await executeToolCall({ id: 'ws_3', name: 'web_search', arguments: { query: 'example' } });
+ expect(result.error).toBeUndefined();
+ expect(result.content).toBeDefined();
+ });
+});
diff --git a/__tests__/unit/services/tools/EmailCalendarExtension.test.ts b/__tests__/unit/services/tools/EmailCalendarExtension.test.ts
new file mode 100644
index 00000000..ea9c4d65
--- /dev/null
+++ b/__tests__/unit/services/tools/EmailCalendarExtension.test.ts
@@ -0,0 +1,179 @@
+/**
+ * EmailCalendarExtension Unit Tests
+ *
+ * The email + calendar tools are pro-gated and implemented in the pro package
+ * as a ToolExtension. These tests cover the tool definitions, enabled-state
+ * filtering (read from the core enabledTools setting), and execution against a
+ * mocked calendar native module and mailto link.
+ */
+
+import { Linking } from 'react-native';
+import type { ToolCall } from '../../../../src/services/tools/types';
+import type { ToolExtension } from '../../../../src/services/tools/extensions';
+
+let mockEnabledTools: string[] = [];
+jest.mock('@offgrid/core/stores', () => ({
+ useAppStore: { getState: () => ({ settings: { enabledTools: mockEnabledTools } }) },
+}));
+
+const mockSaveEvent = jest.fn();
+const mockRequestPermissions = jest.fn();
+const mockFetchAllEvents = jest.fn();
+jest.mock('react-native-calendar-events', () => ({
+ __esModule: true,
+ default: {
+ saveEvent: (...args: any[]) => mockSaveEvent(...args),
+ requestPermissions: (...args: any[]) => mockRequestPermissions(...args),
+ fetchAllEvents: (...args: any[]) => mockFetchAllEvents(...args),
+ },
+}));
+
+// The implementation lives in the private pro submodule, which is not checked
+// out in the open-core CI. Load it dynamically via a computed path (so tsc does
+// not try to resolve the absent module) and skip the suite when it is missing.
+// jest hoists the jest.mock calls above this, so the mocks are already registered.
+function loadProExtension(): ToolExtension | null {
+ const proPath = ['..', '..', '..', '..', 'pro', 'tools', 'EmailCalendarExtension'].join('/');
+ try {
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
+ return require(proPath).EmailCalendarExtension as ToolExtension;
+ } catch {
+ return null;
+ }
+}
+
+const proExtension = loadProExtension();
+const EmailCalendarExtension = proExtension ?? ({} as ToolExtension);
+const describeIfPro = proExtension ? describe : describe.skip;
+
+const mockOpenURL = jest.spyOn(Linking, 'openURL');
+
+function call(name: string, args: Record