@thisispamela/sdk 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -76,7 +76,7 @@ API keys are created and managed through the Pamela Partner Portal or via the Pa
76
76
  -d '{"project_id": "optional-project-id", "key_prefix": "pk_live_"}'
77
77
  ```
78
78
  3. **Save your API key immediately** - the full key is only returned once during creation
79
- 4. **Use the key prefix** (`pk_live_` or `pk_test_`) to identify keys in your account
79
+ 4. **Use the key prefix** (`pk_live_`) to identify keys in your account
80
80
 
81
81
  ### Managing API Keys
82
82
 
@@ -86,8 +86,7 @@ API keys are created and managed through the Pamela Partner Portal or via the Pa
86
86
 
87
87
  ### API Key Format
88
88
 
89
- - **Live keys**: Start with `pk_live_` (for production use)
90
- - **Test keys**: Start with `pk_test_` (for development/testing)
89
+ - **Live keys**: Start with `pk_live_` (all API usage)
91
90
  - **Security**: Keys are hashed in the database. Store them securely and never commit them to version control.
92
91
 
93
92
  ## Subscription Requirements
@@ -112,29 +111,116 @@ Check subscription status using the Enterprise Partner API:
112
111
  - `POST /api/b2b/v1/partner/subscription/checkout` - Create checkout session
113
112
  - `POST /api/b2b/v1/partner/subscription/portal` - Access Customer Portal
114
113
 
115
- ## Error Codes
114
+ ## Error Handling
116
115
 
117
- ### Authentication Errors
116
+ The SDK provides typed exceptions for all API errors:
118
117
 
119
- - `401 Unauthorized`: Invalid or missing API key
120
- - `403 Forbidden`: Subscription inactive or expired
121
- - Check subscription status: `subscription_status` must be `"active"`
122
- - For `past_due`: Check `grace_period_ends_at` - access allowed during grace period
118
+ ```typescript
119
+ import {
120
+ PamelaClient,
121
+ PamelaError,
122
+ AuthenticationError,
123
+ SubscriptionError,
124
+ RateLimitError,
125
+ ValidationError,
126
+ CallError,
127
+ } from '@thisispamela/sdk';
128
+
129
+ const client = new PamelaClient({ apiKey: 'pk_live_your_key' });
130
+
131
+ try {
132
+ const call = await client.createCall({ to: '+1234567890', task: 'Test' });
133
+ } catch (e) {
134
+ if (e instanceof AuthenticationError) {
135
+ // 401: Invalid or missing API key
136
+ console.log('Auth failed:', e.message);
137
+ console.log('Error code:', e.errorCode);
138
+ } else if (e instanceof SubscriptionError) {
139
+ // 403: Subscription inactive or expired
140
+ if (e.errorCode === 7008) {
141
+ console.log('Grace period expired - update payment method');
142
+ } else {
143
+ console.log('Subscription issue:', e.message);
144
+ }
145
+ } else if (e instanceof RateLimitError) {
146
+ // 429: Rate limit exceeded
147
+ const retryAfter = e.details?.retry_after ?? 30;
148
+ console.log(`Rate limited, retry after ${retryAfter}s`);
149
+ } else if (e instanceof ValidationError) {
150
+ // 400/422: Invalid request parameters
151
+ console.log('Invalid request:', e.message);
152
+ console.log('Details:', e.details);
153
+ } else if (e instanceof CallError) {
154
+ // Call-specific errors
155
+ console.log('Call error:', e.message);
156
+ } else if (e instanceof PamelaError) {
157
+ // All other API errors
158
+ console.log(`API error ${e.errorCode}: ${e.message}`);
159
+ }
160
+ }
161
+ ```
162
+
163
+ ### Exception Hierarchy
164
+
165
+ All exceptions extend `PamelaError`:
166
+
167
+ ```
168
+ PamelaError (base)
169
+ ├── AuthenticationError // 401 errors
170
+ ├── SubscriptionError // 403 errors (subscription issues)
171
+ ├── RateLimitError // 429 errors
172
+ ├── ValidationError // 400/422 errors
173
+ └── CallError // Call-specific errors
174
+ ```
175
+
176
+ ### Exception Properties
177
+
178
+ All exceptions have:
179
+ - `message`: Human-readable error message
180
+ - `errorCode?`: Numeric error code (e.g., 7008 for subscription expired)
181
+ - `details?`: Object with additional context
182
+ - `statusCode?`: HTTP status code
183
+
184
+ ## Error Codes Reference
185
+
186
+ ### Authentication Errors (401)
187
+
188
+ | Code | Description |
189
+ |------|-------------|
190
+ | 1001 | API key required |
191
+ | 1002 | Invalid API key |
192
+ | 1003 | API key expired |
193
+
194
+ ### Subscription Errors (403)
195
+
196
+ | Code | Description |
197
+ |------|-------------|
198
+ | 1005 | Enterprise subscription required |
199
+ | 7008 | Subscription expired (grace period ended) |
123
200
 
124
- ### Validation Errors
201
+ ### Validation Errors (400)
125
202
 
126
- - `400 Bad Request`: Invalid request parameters
127
- - `404 Not Found`: Resource not found (call, partner, etc.)
203
+ | Code | Description |
204
+ |------|-------------|
205
+ | 2001 | Validation error |
206
+ | 2002 | Invalid phone number format |
128
207
 
129
- ### Quota/Limit Errors
208
+ ### Enterprise Errors (7xxx)
130
209
 
131
- - `429 Too Many Requests`: Quota exceeded (if quota limits are set)
132
- - Note: Enterprise subscriptions have no quota limits for API calls (all usage is billed per-minute)
210
+ | Code | Description |
211
+ |------|-------------|
212
+ | 7001 | Partner not found |
213
+ | 7002 | Project not found |
214
+ | 7003 | Call not found |
215
+ | 7004 | No phone number for country |
216
+ | 7005 | Unsupported country |
133
217
 
134
- ### Rate Limit Errors
218
+ ### Rate Limiting (429)
135
219
 
136
- - `429 Too Many Requests`: Rate limit exceeded
137
- - Retry after the time specified in `Retry-After` header
220
+ | Code | Description |
221
+ |------|-------------|
222
+ | 6001 | Rate limit exceeded |
223
+ | 6002 | Quota exceeded |
138
224
 
139
225
  ## Usage Limits & Billing
140
226
 
@@ -0,0 +1,45 @@
1
+ export declare class PamelaError extends Error {
2
+ errorCode?: number;
3
+ details?: Record<string, any>;
4
+ statusCode?: number;
5
+ constructor(message: string, options?: {
6
+ errorCode?: number;
7
+ details?: Record<string, any>;
8
+ statusCode?: number;
9
+ });
10
+ }
11
+ export declare class AuthenticationError extends PamelaError {
12
+ constructor(message: string, options?: {
13
+ errorCode?: number;
14
+ details?: Record<string, any>;
15
+ statusCode?: number;
16
+ });
17
+ }
18
+ export declare class SubscriptionError extends PamelaError {
19
+ constructor(message: string, options?: {
20
+ errorCode?: number;
21
+ details?: Record<string, any>;
22
+ statusCode?: number;
23
+ });
24
+ }
25
+ export declare class RateLimitError extends PamelaError {
26
+ constructor(message: string, options?: {
27
+ errorCode?: number;
28
+ details?: Record<string, any>;
29
+ statusCode?: number;
30
+ });
31
+ }
32
+ export declare class ValidationError extends PamelaError {
33
+ constructor(message: string, options?: {
34
+ errorCode?: number;
35
+ details?: Record<string, any>;
36
+ statusCode?: number;
37
+ });
38
+ }
39
+ export declare class CallError extends PamelaError {
40
+ constructor(message: string, options?: {
41
+ errorCode?: number;
42
+ details?: Record<string, any>;
43
+ statusCode?: number;
44
+ });
45
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CallError = exports.ValidationError = exports.RateLimitError = exports.SubscriptionError = exports.AuthenticationError = exports.PamelaError = void 0;
4
+ class PamelaError extends Error {
5
+ constructor(message, options) {
6
+ super(message);
7
+ this.name = 'PamelaError';
8
+ this.errorCode = options?.errorCode;
9
+ this.details = options?.details;
10
+ this.statusCode = options?.statusCode;
11
+ }
12
+ }
13
+ exports.PamelaError = PamelaError;
14
+ class AuthenticationError extends PamelaError {
15
+ constructor(message, options) {
16
+ super(message, options);
17
+ this.name = 'AuthenticationError';
18
+ }
19
+ }
20
+ exports.AuthenticationError = AuthenticationError;
21
+ class SubscriptionError extends PamelaError {
22
+ constructor(message, options) {
23
+ super(message, options);
24
+ this.name = 'SubscriptionError';
25
+ }
26
+ }
27
+ exports.SubscriptionError = SubscriptionError;
28
+ class RateLimitError extends PamelaError {
29
+ constructor(message, options) {
30
+ super(message, options);
31
+ this.name = 'RateLimitError';
32
+ }
33
+ }
34
+ exports.RateLimitError = RateLimitError;
35
+ class ValidationError extends PamelaError {
36
+ constructor(message, options) {
37
+ super(message, options);
38
+ this.name = 'ValidationError';
39
+ }
40
+ }
41
+ exports.ValidationError = ValidationError;
42
+ class CallError extends PamelaError {
43
+ constructor(message, options) {
44
+ super(message, options);
45
+ this.name = 'CallError';
46
+ }
47
+ }
48
+ exports.CallError = CallError;
package/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@
2
2
  * Pamela Enterprise Voice API SDK for JavaScript/TypeScript
3
3
  */
4
4
  import { AxiosInstance } from 'axios';
5
+ import { PamelaError, AuthenticationError, SubscriptionError, RateLimitError, ValidationError, CallError } from './errors';
5
6
  export interface PamelaClientConfig {
6
7
  apiKey: string;
7
8
  baseUrl?: string;
@@ -150,3 +151,4 @@ export declare class PamelaClient {
150
151
  }
151
152
  export default PamelaClient;
152
153
  export { PamelaClient as Pamela };
154
+ export { PamelaError, AuthenticationError, SubscriptionError, RateLimitError, ValidationError, CallError, };
package/dist/index.js CHANGED
@@ -39,9 +39,52 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
39
39
  return (mod && mod.__esModule) ? mod : { "default": mod };
40
40
  };
41
41
  Object.defineProperty(exports, "__esModule", { value: true });
42
- exports.Pamela = exports.PamelaClient = exports.UsageClient = void 0;
42
+ exports.CallError = exports.ValidationError = exports.RateLimitError = exports.SubscriptionError = exports.AuthenticationError = exports.PamelaError = exports.Pamela = exports.PamelaClient = exports.UsageClient = void 0;
43
43
  const axios_1 = __importDefault(require("axios"));
44
44
  const crypto = __importStar(require("crypto"));
45
+ const errors_1 = require("./errors");
46
+ Object.defineProperty(exports, "PamelaError", { enumerable: true, get: function () { return errors_1.PamelaError; } });
47
+ Object.defineProperty(exports, "AuthenticationError", { enumerable: true, get: function () { return errors_1.AuthenticationError; } });
48
+ Object.defineProperty(exports, "SubscriptionError", { enumerable: true, get: function () { return errors_1.SubscriptionError; } });
49
+ Object.defineProperty(exports, "RateLimitError", { enumerable: true, get: function () { return errors_1.RateLimitError; } });
50
+ Object.defineProperty(exports, "ValidationError", { enumerable: true, get: function () { return errors_1.ValidationError; } });
51
+ Object.defineProperty(exports, "CallError", { enumerable: true, get: function () { return errors_1.CallError; } });
52
+ const mapAxiosError = (error, endpoint) => {
53
+ const statusCode = error.response?.status;
54
+ const data = error.response?.data;
55
+ let errorCode;
56
+ let message;
57
+ let details;
58
+ if (data && typeof data === 'object') {
59
+ const detail = data.detail;
60
+ if (detail && typeof detail === 'object') {
61
+ errorCode = detail.error_code ?? detail.error?.code;
62
+ message = detail.message ?? detail.error?.message;
63
+ details = detail.details ?? detail.error?.details;
64
+ }
65
+ else {
66
+ errorCode = data.error_code ?? data.error?.code;
67
+ message = data.message ?? data.detail;
68
+ details = data.details ?? data.error?.details;
69
+ }
70
+ }
71
+ if (!message) {
72
+ message = error.message || 'Request failed';
73
+ }
74
+ const options = { errorCode, details, statusCode };
75
+ if (statusCode === 401)
76
+ return new errors_1.AuthenticationError(message, options);
77
+ if (statusCode === 403)
78
+ return new errors_1.SubscriptionError(message, options);
79
+ if (statusCode === 429)
80
+ return new errors_1.RateLimitError(message, options);
81
+ if (statusCode === 400 || statusCode === 422)
82
+ return new errors_1.ValidationError(message, options);
83
+ if (endpoint?.startsWith('/calls')) {
84
+ return new errors_1.CallError(message, options);
85
+ }
86
+ return new errors_1.PamelaError(message, options);
87
+ };
45
88
  class UsageClient {
46
89
  constructor(client) {
47
90
  this.client = client;
@@ -81,7 +124,7 @@ class PamelaClient {
81
124
  await new Promise(resolve => setTimeout(resolve, 1000 * config.retry));
82
125
  return this.client.request(config);
83
126
  }
84
- return Promise.reject(error);
127
+ return Promise.reject(mapAxiosError(error, config?.url));
85
128
  });
86
129
  }
87
130
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thisispamela/sdk",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Pamela Enterprise Voice API SDK for JavaScript/TypeScript",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/src/errors.ts ADDED
@@ -0,0 +1,48 @@
1
+ export class PamelaError extends Error {
2
+ public errorCode?: number;
3
+ public details?: Record<string, any>;
4
+ public statusCode?: number;
5
+
6
+ constructor(message: string, options?: { errorCode?: number; details?: Record<string, any>; statusCode?: number }) {
7
+ super(message);
8
+ this.name = 'PamelaError';
9
+ this.errorCode = options?.errorCode;
10
+ this.details = options?.details;
11
+ this.statusCode = options?.statusCode;
12
+ }
13
+ }
14
+
15
+ export class AuthenticationError extends PamelaError {
16
+ constructor(message: string, options?: { errorCode?: number; details?: Record<string, any>; statusCode?: number }) {
17
+ super(message, options);
18
+ this.name = 'AuthenticationError';
19
+ }
20
+ }
21
+
22
+ export class SubscriptionError extends PamelaError {
23
+ constructor(message: string, options?: { errorCode?: number; details?: Record<string, any>; statusCode?: number }) {
24
+ super(message, options);
25
+ this.name = 'SubscriptionError';
26
+ }
27
+ }
28
+
29
+ export class RateLimitError extends PamelaError {
30
+ constructor(message: string, options?: { errorCode?: number; details?: Record<string, any>; statusCode?: number }) {
31
+ super(message, options);
32
+ this.name = 'RateLimitError';
33
+ }
34
+ }
35
+
36
+ export class ValidationError extends PamelaError {
37
+ constructor(message: string, options?: { errorCode?: number; details?: Record<string, any>; statusCode?: number }) {
38
+ super(message, options);
39
+ this.name = 'ValidationError';
40
+ }
41
+ }
42
+
43
+ export class CallError extends PamelaError {
44
+ constructor(message: string, options?: { errorCode?: number; details?: Record<string, any>; statusCode?: number }) {
45
+ super(message, options);
46
+ this.name = 'CallError';
47
+ }
48
+ }
package/src/index.ts CHANGED
@@ -4,6 +4,14 @@
4
4
 
5
5
  import axios, { AxiosInstance, AxiosError } from 'axios';
6
6
  import * as crypto from 'crypto';
7
+ import {
8
+ PamelaError,
9
+ AuthenticationError,
10
+ SubscriptionError,
11
+ RateLimitError,
12
+ ValidationError,
13
+ CallError,
14
+ } from './errors';
7
15
 
8
16
  export interface PamelaClientConfig {
9
17
  apiKey: string;
@@ -64,6 +72,43 @@ export interface WebhookPayload {
64
72
  data: Record<string, any>;
65
73
  }
66
74
 
75
+ const mapAxiosError = (error: AxiosError, endpoint?: string): PamelaError => {
76
+ const statusCode = error.response?.status;
77
+ const data = error.response?.data as any;
78
+ let errorCode: number | undefined;
79
+ let message: string | undefined;
80
+ let details: Record<string, any> | undefined;
81
+
82
+ if (data && typeof data === 'object') {
83
+ const detail = data.detail;
84
+ if (detail && typeof detail === 'object') {
85
+ errorCode = detail.error_code ?? detail.error?.code;
86
+ message = detail.message ?? detail.error?.message;
87
+ details = detail.details ?? detail.error?.details;
88
+ } else {
89
+ errorCode = data.error_code ?? data.error?.code;
90
+ message = data.message ?? data.detail;
91
+ details = data.details ?? data.error?.details;
92
+ }
93
+ }
94
+
95
+ if (!message) {
96
+ message = error.message || 'Request failed';
97
+ }
98
+
99
+ const options = { errorCode, details, statusCode };
100
+ if (statusCode === 401) return new AuthenticationError(message, options);
101
+ if (statusCode === 403) return new SubscriptionError(message, options);
102
+ if (statusCode === 429) return new RateLimitError(message, options);
103
+ if (statusCode === 400 || statusCode === 422) return new ValidationError(message, options);
104
+
105
+ if (endpoint?.startsWith('/calls')) {
106
+ return new CallError(message, options);
107
+ }
108
+
109
+ return new PamelaError(message, options);
110
+ };
111
+
67
112
  export class UsageClient {
68
113
  private client: AxiosInstance;
69
114
 
@@ -127,7 +172,7 @@ export class PamelaClient {
127
172
  return this.client.request(config);
128
173
  }
129
174
 
130
- return Promise.reject(error);
175
+ return Promise.reject(mapAxiosError(error, config?.url));
131
176
  }
132
177
  );
133
178
  }
@@ -225,4 +270,12 @@ export class PamelaClient {
225
270
  // Export as both default and named export for flexibility
226
271
  export default PamelaClient;
227
272
  export { PamelaClient as Pamela };
273
+ export {
274
+ PamelaError,
275
+ AuthenticationError,
276
+ SubscriptionError,
277
+ RateLimitError,
278
+ ValidationError,
279
+ CallError,
280
+ };
228
281
 
@@ -1,20 +1,171 @@
1
1
  /**
2
- * Integration tests for Pamela JavaScript SDK.
2
+ * Tests for Pamela JavaScript/TypeScript SDK.
3
3
  *
4
- * These tests run against staging only when env vars are provided.
4
+ * Includes unit tests (no network) and integration tests (require env vars).
5
5
  */
6
6
 
7
- import PamelaClient from "../src/index";
7
+ import PamelaClient, {
8
+ PamelaError,
9
+ AuthenticationError,
10
+ SubscriptionError,
11
+ RateLimitError,
12
+ ValidationError,
13
+ CallError,
14
+ } from "../src/index";
15
+ import * as crypto from "crypto";
8
16
 
9
17
  const TEST_API_URL =
10
18
  process.env.PAMELA_API_URL || "https://pamela-dev.up.railway.app";
11
19
  const TEST_API_KEY = process.env.PAMELA_TEST_API_KEY;
12
20
  const SHOULD_RUN = Boolean(TEST_API_KEY);
13
21
 
22
+ // =============================================================================
23
+ // Unit Tests (No Network Required)
24
+ // =============================================================================
25
+
26
+ describe("Webhook Signature Verification", () => {
27
+ const secret = "test_secret_123";
28
+
29
+ it("verifies valid signature", () => {
30
+ const payload = { event: "call.completed", call_id: "call_123" };
31
+ const payloadStr = JSON.stringify(payload);
32
+ const signature = crypto
33
+ .createHmac("sha256", secret)
34
+ .update(payloadStr)
35
+ .digest("hex");
36
+
37
+ expect(
38
+ PamelaClient.verifyWebhookSignature(payload, signature, secret)
39
+ ).toBe(true);
40
+ });
41
+
42
+ it("rejects invalid signature", () => {
43
+ const payload = { event: "call.completed" };
44
+ expect(
45
+ PamelaClient.verifyWebhookSignature(payload, "invalid_sig", secret)
46
+ ).toBe(false);
47
+ });
48
+
49
+ it("handles string payload", () => {
50
+ const payloadStr = '{"test":"value"}';
51
+ const signature = crypto
52
+ .createHmac("sha256", secret)
53
+ .update(payloadStr)
54
+ .digest("hex");
55
+
56
+ expect(
57
+ PamelaClient.verifyWebhookSignature(payloadStr, signature, secret)
58
+ ).toBe(true);
59
+ });
60
+ });
61
+
62
+ describe("Exception Classes", () => {
63
+ it("PamelaError has correct properties", () => {
64
+ const error = new PamelaError("Test error", {
65
+ errorCode: 1001,
66
+ details: { key: "value" },
67
+ statusCode: 403,
68
+ });
69
+
70
+ expect(error.message).toBe("Test error");
71
+ expect(error.errorCode).toBe(1001);
72
+ expect(error.details).toEqual({ key: "value" });
73
+ expect(error.statusCode).toBe(403);
74
+ expect(error.name).toBe("PamelaError");
75
+ });
76
+
77
+ it("AuthenticationError has correct name", () => {
78
+ const error = new AuthenticationError("Invalid API key");
79
+ expect(error.name).toBe("AuthenticationError");
80
+ expect(error instanceof PamelaError).toBe(true);
81
+ });
82
+
83
+ it("SubscriptionError has correct name", () => {
84
+ const error = new SubscriptionError("Subscription expired", {
85
+ errorCode: 7008,
86
+ });
87
+ expect(error.name).toBe("SubscriptionError");
88
+ expect(error.errorCode).toBe(7008);
89
+ });
90
+
91
+ it("RateLimitError has correct name", () => {
92
+ const error = new RateLimitError("Rate limit exceeded", { statusCode: 429 });
93
+ expect(error.name).toBe("RateLimitError");
94
+ expect(error.statusCode).toBe(429);
95
+ });
96
+
97
+ it("ValidationError has correct name", () => {
98
+ const error = new ValidationError("Invalid phone number", {
99
+ details: { field: "to" },
100
+ });
101
+ expect(error.name).toBe("ValidationError");
102
+ expect(error.details).toEqual({ field: "to" });
103
+ });
104
+
105
+ it("CallError has correct name", () => {
106
+ const error = new CallError("Call failed");
107
+ expect(error.name).toBe("CallError");
108
+ expect(error instanceof PamelaError).toBe(true);
109
+ });
110
+
111
+ it("exceptions can be created with minimal args", () => {
112
+ const error = new PamelaError("Simple error");
113
+ expect(error.message).toBe("Simple error");
114
+ expect(error.errorCode).toBeUndefined();
115
+ expect(error.details).toBeUndefined();
116
+ });
117
+ });
118
+
119
+ describe("Client Initialization", () => {
120
+ it("initializes with API key", () => {
121
+ const client = new PamelaClient({
122
+ apiKey: "pk_live_test",
123
+ });
124
+ expect(client).toBeDefined();
125
+ expect(client.usage).toBeDefined();
126
+ });
127
+
128
+ it("uses default base URL", () => {
129
+ const client = new PamelaClient({
130
+ apiKey: "pk_live_test",
131
+ });
132
+ // Client is created successfully with default URL
133
+ expect(client).toBeDefined();
134
+ });
135
+
136
+ it("accepts custom base URL", () => {
137
+ const client = new PamelaClient({
138
+ apiKey: "pk_live_test",
139
+ baseUrl: "https://custom.api.com",
140
+ });
141
+ expect(client).toBeDefined();
142
+ });
143
+ });
144
+
145
+ describe("Exception Hierarchy", () => {
146
+ it("all exceptions inherit from PamelaError", () => {
147
+ expect(new AuthenticationError("test") instanceof PamelaError).toBe(true);
148
+ expect(new SubscriptionError("test") instanceof PamelaError).toBe(true);
149
+ expect(new RateLimitError("test") instanceof PamelaError).toBe(true);
150
+ expect(new ValidationError("test") instanceof PamelaError).toBe(true);
151
+ expect(new CallError("test") instanceof PamelaError).toBe(true);
152
+ });
153
+
154
+ it("all exceptions inherit from Error", () => {
155
+ expect(new PamelaError("test") instanceof Error).toBe(true);
156
+ expect(new AuthenticationError("test") instanceof Error).toBe(true);
157
+ expect(new SubscriptionError("test") instanceof Error).toBe(true);
158
+ });
159
+ });
160
+
161
+ // =============================================================================
162
+ // Integration Tests (Require PAMELA_TEST_API_KEY)
163
+ // =============================================================================
164
+
14
165
  describe("PamelaClient SDK", () => {
15
166
  it("initializes with API key", () => {
16
167
  const client = new PamelaClient({
17
- apiKey: TEST_API_KEY || "pk_test_placeholder",
168
+ apiKey: TEST_API_KEY || "pk_live_placeholder",
18
169
  baseUrl: TEST_API_URL,
19
170
  });
20
171
  expect(client).toBeDefined();
@@ -42,5 +193,10 @@ describe("PamelaClient SDK", () => {
42
193
  expect(usage).toBeDefined();
43
194
  expect(usage.call_count).toBeDefined();
44
195
  });
196
+
197
+ it("lists tools", async () => {
198
+ const tools = await client.listTools();
199
+ expect(Array.isArray(tools)).toBe(true);
200
+ });
45
201
  });
46
202
 
package/tests/setup.ts CHANGED
@@ -7,8 +7,8 @@
7
7
  // Set test API URL (use staging or mock server)
8
8
  export const TEST_API_URL = process.env.TEST_API_URL || "https://pamela-dev.up.railway.app";
9
9
 
10
- // Test API key (use test keys from staging environment)
11
- export const TEST_API_KEY = process.env.TEST_API_KEY || "pk_test_placeholder";
10
+ // API key for tests (use a staging key)
11
+ export const TEST_API_KEY = process.env.TEST_API_KEY || "pk_live_placeholder";
12
12
 
13
13
  // Mock data helpers
14
14
  export const mockCallResponse = {