@alphabin/trx
Version:
TRX reporter for Playwright tests with Azure Blob Storage upload support
211 lines (210 loc) • 7.76 kB
JavaScript
;
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;