tdpw
Version:
CLI tool for uploading Playwright test reports to TestDino platform with Azure storage support
159 lines • 5.72 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ApiClient = void 0;
const types_1 = require("../types");
// Upload endpoint relative to base API URL
const UPLOAD_ENDPOINT = '/api/reports/playwright';
/**
* Client for communicating with the TestDino API
*/
class ApiClient {
baseUrl;
apiKey;
constructor(config) {
this.baseUrl = config.apiUrl;
this.apiKey = config.token;
}
/**
* Headers to include on every API request
*/
getHeaders() {
return {
'Content-Type': 'application/json',
'User-Agent': 'tdpw/1.0.0',
'X-API-Key': this.apiKey,
};
}
/**
* Upload a JSON payload to the TestDino API with enhanced error handling
* @param payload The data to upload (report + metadata)
*/
async uploadReport(payload) {
const url = `${this.baseUrl}${UPLOAD_ENDPOINT}`;
let response;
try {
response = await fetch(url, {
method: 'POST',
headers: this.getHeaders(),
body: JSON.stringify(payload),
});
}
catch (error) {
// Handle network-level errors (DNS, connection refused, etc.)
const errorMessage = error instanceof Error ? error.message : 'Unknown network error';
throw new types_1.NetworkError(`Failed to connect to TestDino API: ${errorMessage}`);
}
// Handle HTTP error responses
if (!response.ok) {
await this.handleHttpError(response);
}
// Parse JSON response
let json;
try {
json = await response.json();
}
catch (error) {
throw new types_1.NetworkError(`Invalid JSON response from API: ${error instanceof Error ? error.message : 'Parse error'}`);
}
// Extract response data
const result = this.parseUploadResponse(json);
return result;
}
/**
* Handle HTTP error responses with detailed error messages
*/
async handleHttpError(response) {
let errorBody;
try {
errorBody = await response.text();
}
catch {
errorBody = 'Unable to read error response';
}
switch (response.status) {
case 401:
throw new types_1.AuthenticationError('Invalid API key or unauthorized access');
case 403:
throw new types_1.AuthenticationError('API key does not have permission to upload reports');
case 400:
throw new types_1.NetworkError(`Bad request - Invalid data format: ${errorBody}`);
case 413:
throw new types_1.NetworkError('Report payload too large - consider uploading without HTML/traces');
case 429:
throw new types_1.NetworkError('Rate limit exceeded - please wait before retrying');
case 500:
case 502:
case 503:
case 504:
throw new types_1.NetworkError(`TestDino API server error (${response.status}) - please try again later`);
default:
throw new types_1.NetworkError(`HTTP ${response.status}: ${errorBody || response.statusText}`);
}
}
/**
* Parse and validate the upload response
*/
parseUploadResponse(json) {
if (!json || typeof json !== 'object') {
throw new types_1.NetworkError('Invalid response format - expected JSON object');
}
// Handle different response structures
let responseData;
// Check for direct response
if ('testRunId' in json) {
responseData = json;
}
// Check for wrapped response
else if ('data' in json && json.data && typeof json.data === 'object' && 'testRunId' in json.data) {
responseData = json.data;
}
// Check for success wrapper
else if ('success' in json && 'result' in json && json.result && typeof json.result === 'object' && 'testRunId' in json.result) {
responseData = json.result;
}
else {
throw new types_1.NetworkError(`Unexpected API response structure: ${JSON.stringify(json)}`);
}
// Validate required fields
if (!responseData.testRunId || typeof responseData.testRunId !== 'string') {
throw new types_1.NetworkError('API response missing required testRunId field');
}
// Build response object
const result = {
testRunId: responseData.testRunId,
};
// Add optional fields if present
if (responseData.viewUrl && typeof responseData.viewUrl === 'string') {
result.viewUrl = responseData.viewUrl;
}
if (responseData.status && typeof responseData.status === 'string') {
result.status = responseData.status;
}
// Include any additional fields
Object.keys(responseData).forEach(key => {
if (!['testRunId', 'viewUrl', 'status'].includes(key)) {
result[key] = responseData[key];
}
});
return result;
}
/**
* Health check endpoint to verify API connectivity
*/
async healthCheck() {
try {
const response = await fetch(`${this.baseUrl}/api/health`, {
method: 'GET',
headers: {
'User-Agent': 'tdpw/1.0.0',
},
});
return response.ok;
}
catch {
return false;
}
}
}
exports.ApiClient = ApiClient;
//# sourceMappingURL=api.js.map