@thisispamela/sdk 1.0.0 → 1.0.1
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 +152 -5
- package/dist/index.d.ts +23 -1
- package/dist/index.js +26 -3
- package/package.json +3 -3
- package/src/index.ts +41 -2
- package/tests/pamela.test.ts +28 -118
package/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# Pamela SDK for JavaScript/TypeScript
|
|
2
2
|
|
|
3
|
-
Official SDK for the Pamela Voice API.
|
|
3
|
+
Official SDK for the Pamela Enterprise Voice API.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm install @
|
|
8
|
+
npm install @thisispamela/sdk
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
## Usage
|
|
@@ -13,7 +13,7 @@ npm install @pamela/sdk
|
|
|
13
13
|
### Basic Example
|
|
14
14
|
|
|
15
15
|
```typescript
|
|
16
|
-
import { PamelaClient } from '@
|
|
16
|
+
import { PamelaClient } from '@thisispamela/sdk';
|
|
17
17
|
|
|
18
18
|
const client = new PamelaClient({
|
|
19
19
|
apiKey: 'pk_live_your_api_key_here',
|
|
@@ -37,7 +37,7 @@ console.log('Call status:', status.status);
|
|
|
37
37
|
### Webhook Verification
|
|
38
38
|
|
|
39
39
|
```typescript
|
|
40
|
-
import { PamelaClient } from '@
|
|
40
|
+
import { PamelaClient } from '@thisispamela/sdk';
|
|
41
41
|
import express from 'express';
|
|
42
42
|
|
|
43
43
|
const app = express();
|
|
@@ -59,7 +59,154 @@ app.post('/webhooks/pamela', express.json(), (req, res) => {
|
|
|
59
59
|
});
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
+
## Getting API Keys
|
|
63
|
+
|
|
64
|
+
### Obtaining Your API Key
|
|
65
|
+
|
|
66
|
+
API keys are created and managed through the Pamela Partner Portal or via the Partner API:
|
|
67
|
+
|
|
68
|
+
1. **Sign up for an Enterprise subscription** (see Subscription Requirements below)
|
|
69
|
+
2. **Create an API key** via one of these methods:
|
|
70
|
+
- Partner Portal: Log in to your account and navigate to the Enterprise panel
|
|
71
|
+
- Partner API: `POST /api/b2b/v1/partner/api-keys`
|
|
72
|
+
```bash
|
|
73
|
+
curl -X POST https://api.thisispamela.com/api/b2b/v1/partner/api-keys \
|
|
74
|
+
-H "Authorization: Bearer YOUR_B2C_USER_TOKEN" \
|
|
75
|
+
-H "Content-Type: application/json" \
|
|
76
|
+
-d '{"project_id": "optional-project-id", "key_prefix": "pk_live_"}'
|
|
77
|
+
```
|
|
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
|
|
80
|
+
|
|
81
|
+
### Managing API Keys
|
|
82
|
+
|
|
83
|
+
- **List API keys**: `GET /api/b2b/v1/partner/api-keys`
|
|
84
|
+
- **Revoke API key**: `POST /api/b2b/v1/partner/api-keys/{key_id}/revoke`
|
|
85
|
+
- **Associate with projects**: Optionally link API keys to specific projects for better organization
|
|
86
|
+
|
|
87
|
+
### API Key Format
|
|
88
|
+
|
|
89
|
+
- **Live keys**: Start with `pk_live_` (for production use)
|
|
90
|
+
- **Test keys**: Start with `pk_test_` (for development/testing)
|
|
91
|
+
- **Security**: Keys are hashed in the database. Store them securely and never commit them to version control.
|
|
92
|
+
|
|
93
|
+
## Subscription Requirements
|
|
94
|
+
|
|
95
|
+
### Enterprise Subscription Required
|
|
96
|
+
|
|
97
|
+
**All API access requires an active Enterprise subscription.** API calls will return `403 Forbidden` if:
|
|
98
|
+
- No Enterprise subscription is active
|
|
99
|
+
- Subscription status is `past_due` and grace period has expired
|
|
100
|
+
- Subscription status is `canceled`
|
|
101
|
+
|
|
102
|
+
### Grace Period
|
|
103
|
+
|
|
104
|
+
Enterprise subscriptions have a **1-week grace period** when payment fails:
|
|
105
|
+
- During grace period: API access is allowed, but usage is still charged
|
|
106
|
+
- After grace period expires: API access is blocked until payment is updated
|
|
107
|
+
|
|
108
|
+
### Subscription Status Endpoints
|
|
109
|
+
|
|
110
|
+
Check subscription status using the Enterprise Partner API:
|
|
111
|
+
- `GET /api/b2b/v1/partner/subscription` - Get subscription status
|
|
112
|
+
- `POST /api/b2b/v1/partner/subscription/checkout` - Create checkout session
|
|
113
|
+
- `POST /api/b2b/v1/partner/subscription/portal` - Access Customer Portal
|
|
114
|
+
|
|
115
|
+
## Error Codes
|
|
116
|
+
|
|
117
|
+
### Authentication Errors
|
|
118
|
+
|
|
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
|
|
123
|
+
|
|
124
|
+
### Validation Errors
|
|
125
|
+
|
|
126
|
+
- `400 Bad Request`: Invalid request parameters
|
|
127
|
+
- `404 Not Found`: Resource not found (call, partner, etc.)
|
|
128
|
+
|
|
129
|
+
### Quota/Limit Errors
|
|
130
|
+
|
|
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)
|
|
133
|
+
|
|
134
|
+
### Rate Limit Errors
|
|
135
|
+
|
|
136
|
+
- `429 Too Many Requests`: Rate limit exceeded
|
|
137
|
+
- Retry after the time specified in `Retry-After` header
|
|
138
|
+
|
|
139
|
+
## Usage Limits & Billing
|
|
140
|
+
|
|
141
|
+
### Enterprise API Usage
|
|
142
|
+
|
|
143
|
+
- **Unlimited API calls** (no call count limits)
|
|
144
|
+
- **All API usage billed at $0.10/minute** (10 cents per minute)
|
|
145
|
+
- **Minimum billing: 1 minute per call** (even if call duration < 60 seconds)
|
|
146
|
+
- **Billing calculation**: `billed_minutes = max(ceil(duration_seconds / 60), 1)`
|
|
147
|
+
- **Only calls that connect** (have `started_at`) are billed
|
|
148
|
+
|
|
149
|
+
### Usage Tracking
|
|
150
|
+
|
|
151
|
+
- Usage is tracked in `b2b_usage` collection with `type: "api_usage"` (collection name stays `b2b_usage`)
|
|
152
|
+
- Usage is synced to Stripe hourly (at :00 minutes)
|
|
153
|
+
- Stripe meter name: `stripe_minutes`
|
|
154
|
+
- Failed syncs are retried with exponential backoff (1s, 2s, 4s, 8s, 16s), max 5 retries
|
|
155
|
+
|
|
156
|
+
### Billing Period
|
|
157
|
+
|
|
158
|
+
- Billing is based on calendar months (UTC midnight on 1st of each month)
|
|
159
|
+
- Calls are billed in the month where `started_at` occurred
|
|
160
|
+
- Usage sync status: `pending`, `synced`, or `failed`
|
|
161
|
+
|
|
162
|
+
## API Methods
|
|
163
|
+
|
|
164
|
+
### Calls
|
|
165
|
+
|
|
166
|
+
- `createCall(request)` - Create a new call
|
|
167
|
+
- `getCall(callId)` - Get call status and details
|
|
168
|
+
- `listCalls(params?)` - List calls with optional filters
|
|
169
|
+
- `cancelCall(callId)` - Cancel an in-progress call
|
|
170
|
+
|
|
171
|
+
### Tools
|
|
172
|
+
|
|
173
|
+
- `registerTool(tool)` - Register a tool
|
|
174
|
+
- `listTools()` - List all tools
|
|
175
|
+
- `deleteTool(toolId)` - Delete a tool
|
|
176
|
+
|
|
177
|
+
### Usage
|
|
178
|
+
|
|
179
|
+
- `usage.get(period?)` - Get usage statistics
|
|
180
|
+
|
|
181
|
+
**Example:**
|
|
182
|
+
```typescript
|
|
183
|
+
// Get current month usage
|
|
184
|
+
const usage = await client.usage.get();
|
|
185
|
+
|
|
186
|
+
// Get usage for specific period
|
|
187
|
+
const janUsage = await client.usage.get("2024-01");
|
|
188
|
+
|
|
189
|
+
console.log(`Usage: ${usage.call_count} calls, ${usage.api_minutes} minutes`);
|
|
190
|
+
console.log(`Quota: ${usage.quota?.partner_limit || 'Unlimited'}`);
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
**Response:**
|
|
194
|
+
```typescript
|
|
195
|
+
{
|
|
196
|
+
partner_id: "partner_123",
|
|
197
|
+
project_id?: "project_456",
|
|
198
|
+
period: "2024-01",
|
|
199
|
+
call_count: 150,
|
|
200
|
+
quota?: {
|
|
201
|
+
partner_limit?: number,
|
|
202
|
+
project_limit?: number
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**Note:** Enterprise subscriptions have no quota limits - all usage is billed per-minute.
|
|
208
|
+
|
|
62
209
|
## API Reference
|
|
63
210
|
|
|
64
|
-
See the [Pamela
|
|
211
|
+
See the [Pamela Enterprise API Documentation](https://docs.thisispamela.com/enterprise) for full API reference.
|
|
65
212
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Pamela
|
|
2
|
+
* Pamela Enterprise Voice API SDK for JavaScript/TypeScript
|
|
3
3
|
*/
|
|
4
|
+
import { AxiosInstance } from 'axios';
|
|
4
5
|
export interface PamelaClientConfig {
|
|
5
6
|
apiKey: string;
|
|
6
7
|
baseUrl?: string;
|
|
@@ -54,9 +55,28 @@ export interface WebhookPayload {
|
|
|
54
55
|
timestamp: string;
|
|
55
56
|
data: Record<string, any>;
|
|
56
57
|
}
|
|
58
|
+
export declare class UsageClient {
|
|
59
|
+
private client;
|
|
60
|
+
constructor(client: AxiosInstance);
|
|
61
|
+
/**
|
|
62
|
+
* Get usage statistics for partner/project.
|
|
63
|
+
*/
|
|
64
|
+
get(period?: string): Promise<{
|
|
65
|
+
partner_id: string;
|
|
66
|
+
project_id?: string;
|
|
67
|
+
period: string;
|
|
68
|
+
call_count: number;
|
|
69
|
+
last_updated?: string;
|
|
70
|
+
quota?: {
|
|
71
|
+
partner_limit?: number;
|
|
72
|
+
project_limit?: number;
|
|
73
|
+
};
|
|
74
|
+
}>;
|
|
75
|
+
}
|
|
57
76
|
export declare class PamelaClient {
|
|
58
77
|
private client;
|
|
59
78
|
private apiKey;
|
|
79
|
+
usage: UsageClient;
|
|
60
80
|
constructor(config: PamelaClientConfig);
|
|
61
81
|
/**
|
|
62
82
|
* Create a new call.
|
|
@@ -71,6 +91,7 @@ export declare class PamelaClient {
|
|
|
71
91
|
*/
|
|
72
92
|
listCalls(params?: {
|
|
73
93
|
status?: string;
|
|
94
|
+
status_filter?: string;
|
|
74
95
|
limit?: number;
|
|
75
96
|
offset?: number;
|
|
76
97
|
start_date?: string;
|
|
@@ -128,3 +149,4 @@ export declare class PamelaClient {
|
|
|
128
149
|
static verifyWebhookSignature(payload: string | object, signature: string, secret: string): boolean;
|
|
129
150
|
}
|
|
130
151
|
export default PamelaClient;
|
|
152
|
+
export { PamelaClient as Pamela };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* Pamela
|
|
3
|
+
* Pamela Enterprise Voice API SDK for JavaScript/TypeScript
|
|
4
4
|
*/
|
|
5
5
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
6
|
if (k2 === undefined) k2 = k;
|
|
@@ -39,9 +39,23 @@ 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.PamelaClient = void 0;
|
|
42
|
+
exports.Pamela = exports.PamelaClient = exports.UsageClient = void 0;
|
|
43
43
|
const axios_1 = __importDefault(require("axios"));
|
|
44
44
|
const crypto = __importStar(require("crypto"));
|
|
45
|
+
class UsageClient {
|
|
46
|
+
constructor(client) {
|
|
47
|
+
this.client = client;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Get usage statistics for partner/project.
|
|
51
|
+
*/
|
|
52
|
+
async get(period) {
|
|
53
|
+
const params = period ? { period } : {};
|
|
54
|
+
const response = await this.client.get('/usage', { params });
|
|
55
|
+
return response.data;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
exports.UsageClient = UsageClient;
|
|
45
59
|
class PamelaClient {
|
|
46
60
|
constructor(config) {
|
|
47
61
|
this.apiKey = config.apiKey;
|
|
@@ -54,6 +68,8 @@ class PamelaClient {
|
|
|
54
68
|
},
|
|
55
69
|
timeout: 30000,
|
|
56
70
|
});
|
|
71
|
+
// Initialize usage client
|
|
72
|
+
this.usage = new UsageClient(this.client);
|
|
57
73
|
// Add retry logic
|
|
58
74
|
this.client.interceptors.response.use((response) => response, async (error) => {
|
|
59
75
|
const config = error.config;
|
|
@@ -86,7 +102,12 @@ class PamelaClient {
|
|
|
86
102
|
* List calls for the authenticated partner/project.
|
|
87
103
|
*/
|
|
88
104
|
async listCalls(params) {
|
|
89
|
-
const
|
|
105
|
+
const normalizedParams = { ...params };
|
|
106
|
+
if (params?.status_filter || params?.status) {
|
|
107
|
+
normalizedParams.status_filter = params?.status_filter ?? params?.status;
|
|
108
|
+
delete normalizedParams.status;
|
|
109
|
+
}
|
|
110
|
+
const response = await this.client.get('/calls', { params: normalizedParams });
|
|
90
111
|
// Backend returns { items: [...], total, limit, offset }
|
|
91
112
|
return response.data;
|
|
92
113
|
}
|
|
@@ -131,4 +152,6 @@ class PamelaClient {
|
|
|
131
152
|
}
|
|
132
153
|
}
|
|
133
154
|
exports.PamelaClient = PamelaClient;
|
|
155
|
+
exports.Pamela = PamelaClient;
|
|
156
|
+
// Export as both default and named export for flexibility
|
|
134
157
|
exports.default = PamelaClient;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thisispamela/sdk",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "Pamela
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Pamela Enterprise Voice API SDK for JavaScript/TypeScript",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"scripts": {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"pamela",
|
|
15
15
|
"voice",
|
|
16
16
|
"api",
|
|
17
|
-
"
|
|
17
|
+
"enterprise",
|
|
18
18
|
"phone",
|
|
19
19
|
"calling"
|
|
20
20
|
],
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Pamela
|
|
2
|
+
* Pamela Enterprise Voice API SDK for JavaScript/TypeScript
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import axios, { AxiosInstance, AxiosError } from 'axios';
|
|
@@ -64,9 +64,37 @@ export interface WebhookPayload {
|
|
|
64
64
|
data: Record<string, any>;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
export class UsageClient {
|
|
68
|
+
private client: AxiosInstance;
|
|
69
|
+
|
|
70
|
+
constructor(client: AxiosInstance) {
|
|
71
|
+
this.client = client;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Get usage statistics for partner/project.
|
|
76
|
+
*/
|
|
77
|
+
async get(period?: string): Promise<{
|
|
78
|
+
partner_id: string;
|
|
79
|
+
project_id?: string;
|
|
80
|
+
period: string;
|
|
81
|
+
call_count: number;
|
|
82
|
+
last_updated?: string;
|
|
83
|
+
quota?: {
|
|
84
|
+
partner_limit?: number;
|
|
85
|
+
project_limit?: number;
|
|
86
|
+
};
|
|
87
|
+
}> {
|
|
88
|
+
const params = period ? { period } : {};
|
|
89
|
+
const response = await this.client.get('/usage', { params });
|
|
90
|
+
return response.data;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
67
94
|
export class PamelaClient {
|
|
68
95
|
private client: AxiosInstance;
|
|
69
96
|
private apiKey: string;
|
|
97
|
+
public usage: UsageClient;
|
|
70
98
|
|
|
71
99
|
constructor(config: PamelaClientConfig) {
|
|
72
100
|
this.apiKey = config.apiKey;
|
|
@@ -81,6 +109,9 @@ export class PamelaClient {
|
|
|
81
109
|
timeout: 30000,
|
|
82
110
|
});
|
|
83
111
|
|
|
112
|
+
// Initialize usage client
|
|
113
|
+
this.usage = new UsageClient(this.client);
|
|
114
|
+
|
|
84
115
|
// Add retry logic
|
|
85
116
|
this.client.interceptors.response.use(
|
|
86
117
|
(response) => response,
|
|
@@ -122,12 +153,18 @@ export class PamelaClient {
|
|
|
122
153
|
*/
|
|
123
154
|
async listCalls(params?: {
|
|
124
155
|
status?: string;
|
|
156
|
+
status_filter?: string;
|
|
125
157
|
limit?: number;
|
|
126
158
|
offset?: number;
|
|
127
159
|
start_date?: string;
|
|
128
160
|
end_date?: string;
|
|
129
161
|
}): Promise<{ items: CallStatus[]; total: number; limit: number; offset: number }> {
|
|
130
|
-
const
|
|
162
|
+
const normalizedParams = { ...params };
|
|
163
|
+
if (params?.status_filter || params?.status) {
|
|
164
|
+
normalizedParams.status_filter = params?.status_filter ?? params?.status;
|
|
165
|
+
delete (normalizedParams as { status?: string }).status;
|
|
166
|
+
}
|
|
167
|
+
const response = await this.client.get('/calls', { params: normalizedParams });
|
|
131
168
|
// Backend returns { items: [...], total, limit, offset }
|
|
132
169
|
return response.data;
|
|
133
170
|
}
|
|
@@ -185,5 +222,7 @@ export class PamelaClient {
|
|
|
185
222
|
}
|
|
186
223
|
}
|
|
187
224
|
|
|
225
|
+
// Export as both default and named export for flexibility
|
|
188
226
|
export default PamelaClient;
|
|
227
|
+
export { PamelaClient as Pamela };
|
|
189
228
|
|
package/tests/pamela.test.ts
CHANGED
|
@@ -1,136 +1,46 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Integration tests for Pamela JavaScript SDK.
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
*
|
|
4
|
+
* These tests run against staging only when env vars are provided.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import
|
|
7
|
+
import PamelaClient from "../src/index";
|
|
8
8
|
|
|
9
|
-
const TEST_API_URL =
|
|
10
|
-
|
|
9
|
+
const TEST_API_URL =
|
|
10
|
+
process.env.PAMELA_API_URL || "https://pamela-dev.up.railway.app";
|
|
11
|
+
const TEST_API_KEY = process.env.PAMELA_TEST_API_KEY;
|
|
12
|
+
const SHOULD_RUN = Boolean(TEST_API_KEY);
|
|
11
13
|
|
|
12
|
-
describe("
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
apiKey: TEST_API_KEY,
|
|
18
|
-
apiUrl: TEST_API_URL,
|
|
19
|
-
});
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
describe("Initialization", () => {
|
|
23
|
-
it("should initialize with API key", () => {
|
|
24
|
-
expect(sdk).toBeDefined();
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it("should throw error without API key", () => {
|
|
28
|
-
expect(() => {
|
|
29
|
-
new Pamela({ apiKey: "" });
|
|
30
|
-
}).toThrow();
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
describe("Call Creation", () => {
|
|
35
|
-
it("should create a call with required parameters", async () => {
|
|
36
|
-
// Mock or use staging API
|
|
37
|
-
const call = await sdk.calls.create({
|
|
38
|
-
to: "+1234567890",
|
|
39
|
-
from_: "+1987654321",
|
|
40
|
-
country: "US",
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
expect(call).toBeDefined();
|
|
44
|
-
expect(call.id).toBeDefined();
|
|
45
|
-
expect(call.status).toBeDefined();
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it("should handle invalid phone numbers", async () => {
|
|
49
|
-
await expect(
|
|
50
|
-
sdk.calls.create({
|
|
51
|
-
to: "invalid",
|
|
52
|
-
from_: "+1987654321",
|
|
53
|
-
country: "US",
|
|
54
|
-
})
|
|
55
|
-
).rejects.toThrow();
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
describe("Call Status", () => {
|
|
60
|
-
it("should get call status by ID", async () => {
|
|
61
|
-
const callId = "test_call_id";
|
|
62
|
-
const status = await sdk.calls.getStatus(callId);
|
|
63
|
-
|
|
64
|
-
expect(status).toBeDefined();
|
|
65
|
-
expect(status.id).toBe(callId);
|
|
66
|
-
expect(status.status).toBeDefined();
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it("should handle non-existent call ID", async () => {
|
|
70
|
-
await expect(sdk.calls.getStatus("nonexistent")).rejects.toThrow();
|
|
14
|
+
describe("PamelaClient SDK", () => {
|
|
15
|
+
it("initializes with API key", () => {
|
|
16
|
+
const client = new PamelaClient({
|
|
17
|
+
apiKey: TEST_API_KEY || "pk_test_placeholder",
|
|
18
|
+
baseUrl: TEST_API_URL,
|
|
71
19
|
});
|
|
20
|
+
expect(client).toBeDefined();
|
|
72
21
|
});
|
|
22
|
+
});
|
|
73
23
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const callId = "test_call_id";
|
|
77
|
-
const result = await sdk.calls.cancel(callId);
|
|
78
|
-
|
|
79
|
-
expect(result).toBeDefined();
|
|
80
|
-
expect(result.success).toBe(true);
|
|
81
|
-
});
|
|
24
|
+
(SHOULD_RUN ? describe : describe.skip)("PamelaClient integration", () => {
|
|
25
|
+
let client: PamelaClient;
|
|
82
26
|
|
|
83
|
-
|
|
84
|
-
|
|
27
|
+
beforeAll(() => {
|
|
28
|
+
client = new PamelaClient({
|
|
29
|
+
apiKey: TEST_API_KEY as string,
|
|
30
|
+
baseUrl: TEST_API_URL,
|
|
85
31
|
});
|
|
86
32
|
});
|
|
87
33
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
expect(usage).toBeDefined();
|
|
93
|
-
expect(usage.call_count).toBeDefined();
|
|
94
|
-
expect(usage.quota).toBeDefined();
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it("should handle invalid period format", async () => {
|
|
98
|
-
await expect(sdk.usage.get("invalid")).rejects.toThrow();
|
|
99
|
-
});
|
|
34
|
+
it("lists calls", async () => {
|
|
35
|
+
const result = await client.listCalls({ limit: 1 });
|
|
36
|
+
expect(result).toBeDefined();
|
|
37
|
+
expect(Array.isArray(result.items)).toBe(true);
|
|
100
38
|
});
|
|
101
39
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
apiKey: TEST_API_KEY,
|
|
107
|
-
apiUrl: "https://invalid-url.example.com",
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
await expect(
|
|
111
|
-
badSdk.calls.create({
|
|
112
|
-
to: "+1234567890",
|
|
113
|
-
from_: "+1987654321",
|
|
114
|
-
country: "US",
|
|
115
|
-
})
|
|
116
|
-
).rejects.toThrow();
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
it("should handle API errors (400, 401, 403, 500)", async () => {
|
|
120
|
-
// Test with invalid API key
|
|
121
|
-
const badSdk = new Pamela({
|
|
122
|
-
apiKey: "invalid_key",
|
|
123
|
-
apiUrl: TEST_API_URL,
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
await expect(
|
|
127
|
-
badSdk.calls.create({
|
|
128
|
-
to: "+1234567890",
|
|
129
|
-
from_: "+1987654321",
|
|
130
|
-
country: "US",
|
|
131
|
-
})
|
|
132
|
-
).rejects.toThrow();
|
|
133
|
-
});
|
|
40
|
+
it("gets usage", async () => {
|
|
41
|
+
const usage = await client.usage.get();
|
|
42
|
+
expect(usage).toBeDefined();
|
|
43
|
+
expect(usage.call_count).toBeDefined();
|
|
134
44
|
});
|
|
135
45
|
});
|
|
136
46
|
|