UNPKG

@alphabin/trx

Version:

TRX reporter for Playwright tests with Azure Blob Storage upload support

211 lines (210 loc) 7.76 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AzureStorageService = void 0; const axios_1 = __importDefault(require("axios")); const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); // Note: mime-types is an optional dependency, fallback to manual mapping if not available let mime; try { mime = require('mime-types'); } catch { // Fallback mime type mapping mime = { lookup: (fileName) => { const ext = fileName.toLowerCase().split('.').pop(); const mimeMap = { 'html': 'text/html', 'css': 'text/css', 'js': 'application/javascript', 'json': 'application/json', 'png': 'image/png', 'jpg': 'image/jpeg', 'jpeg': 'image/jpeg', 'gif': 'image/gif', 'txt': 'text/plain', 'zip': 'application/zip' }; return mimeMap[ext || ''] || 'application/octet-stream'; } }; } const logger_util_1 = __importDefault(require("../utils/logger.util")); /** * Service for handling Azure Blob Storage operations */ class AzureStorageService { constructor(options) { this.maxRetries = 3; this.serverUrl = options.serverUrl.endsWith('/') ? options.serverUrl.slice(0, -1) : options.serverUrl; this.apiKey = options.apiKey; this.timeout = options.timeout || 30000; } /** * Requests a SAS token from the server */ async requestSasToken() { const url = `${this.serverUrl}/api/storage/token`; const config = { headers: { 'X-API-key': this.apiKey, 'Content-Type': 'application/json' }, timeout: this.timeout }; try { logger_util_1.default.debug(`Requesting SAS token from ${url}`); const response = await axios_1.default.post(url, {}, config); if (response.status >= 200 && response.status < 300 && response.data.success) { logger_util_1.default.debug('SAS token received successfully'); return response.data; } else { logger_util_1.default.error(`Failed to get SAS token: ${response.status} ${response.statusText}`); return null; } } catch (error) { logger_util_1.default.error('Error requesting SAS token', error); return null; } } /** * Uploads a single file to Azure Blob Storage */ async uploadFile(options) { const { filePath, fileName, contentType, sasToken, containerUrl, blobPath } = options; try { // Construct the full blob URL const blobUrl = `${containerUrl}/${blobPath}/${fileName}?${sasToken}`; // Read file content const fileContent = fs_1.default.readFileSync(filePath); // Upload configuration const uploadConfig = { headers: { 'x-ms-blob-type': 'BlockBlob', 'Content-Type': contentType, 'Content-Length': fileContent.length.toString() }, timeout: this.timeout, maxBodyLength: Infinity, maxContentLength: Infinity }; logger_util_1.default.debug(`Uploading file: ${fileName} to ${blobUrl}`); const response = await axios_1.default.put(blobUrl, fileContent, uploadConfig); if (response.status >= 200 && response.status < 300) { const publicUrl = `${containerUrl}/${blobPath}/${fileName}`; logger_util_1.default.debug(`File uploaded successfully: ${fileName}`); return { success: true, fileName, url: publicUrl }; } else { logger_util_1.default.error(`Upload failed for ${fileName}: ${response.status} ${response.statusText}`); return { success: false, fileName, error: `Upload failed: ${response.status} ${response.statusText}` }; } } catch (error) { logger_util_1.default.error(`Error uploading file ${fileName}`, error); return { success: false, fileName, error: error.message }; } } /** * Uploads a file with retry logic */ async uploadFileWithRetry(options) { let lastError; for (let attempt = 1; attempt <= this.maxRetries; attempt++) { logger_util_1.default.debug(`Upload attempt ${attempt}/${this.maxRetries} for ${options.fileName}`); const result = await this.uploadFile(options); if (result.success) { return result; } lastError = result.error; if (attempt < this.maxRetries) { // Wait before retry (exponential backoff: 1s, 2s, 4s) const delay = Math.pow(2, attempt - 1) * 1000; await new Promise(resolve => setTimeout(resolve, delay)); } } return { success: false, fileName: options.fileName, error: lastError || 'Upload failed after retries' }; } /** * Uploads multiple files in parallel */ async uploadFiles(files, sasTokenData) { const { sasToken, containerUrl, blobPath } = sasTokenData; // Create upload promises for parallel execution const uploadPromises = files.map(file => { const uploadOptions = { filePath: file.path, fileName: file.name, contentType: file.contentType, sasToken, containerUrl, blobPath }; return this.uploadFileWithRetry(uploadOptions); }); // Execute all uploads in parallel const results = await Promise.all(uploadPromises); const successful = results.filter(r => r.success).length; const failed = results.filter(r => !r.success).length; logger_util_1.default.debug(`Upload completed: ${successful} successful, ${failed} failed`); return results; } /** * Validates file type against allowed types */ validateFileType(fileName, allowedTypes) { const extension = path_1.default.extname(fileName).toLowerCase().substring(1); return allowedTypes.includes(extension); } /** * Gets content type for a file */ getContentType(fileName) { return mime.lookup(fileName) || 'application/octet-stream'; } /** * Gets file information including size and content type */ getFileInfo(filePath) { try { const stats = fs_1.default.statSync(filePath); const fileName = path_1.default.basename(filePath); return { name: fileName, path: filePath, contentType: this.getContentType(fileName), size: stats.size }; } catch (error) { logger_util_1.default.debug(`Failed to get file info for ${filePath}`, error); return null; } } } exports.AzureStorageService = AzureStorageService; exports.default = AzureStorageService;