UNPKG

jest-allure2-reporter

Version:
208 lines (185 loc) 6.47 kB
import path from 'node:path'; import util from 'node:util'; import type { MaybePromise } from 'jest-allure2-reporter'; import { type Function_, thruMaybePromise } from '../../utils'; import { hijackFunction } from '../../utils'; import type { AttachmentContent, AttachmentContext, AttachmentHandler, AttachmentHandlerResult, AttachmentOptions, ContentAttachmentContext, ContentAttachmentHandler, ContentAttachmentOptions, FileAttachmentContext, FileAttachmentHandler, FileAttachmentOptions, HandlebarsAPI, MIMEInfererContext, } from '../types'; import type { AllureTestItemMetadataProxy } from '../../metadata'; import type { AllureRuntimeContext } from '../AllureRuntimeContext'; import { AllureRuntimeError } from '../../errors'; type AttachmentsModuleContext< Context extends AttachmentContext, Handler extends AttachmentHandler<Context>, > = { readonly handlers: Record<string, Handler>; readonly handlebars: HandlebarsAPI; readonly inferMimeType: (context: MIMEInfererContext) => string | undefined; readonly metadata: AllureTestItemMetadataProxy; readonly outDir: string; readonly waitFor: <T>(promise: Promise<T>) => Promise<T>; }; abstract class AttachmentsModule< Context extends AttachmentContext, Content extends AttachmentContent, Handler extends AttachmentHandler<Context>, Options extends AttachmentOptions<Context>, > { constructor(protected readonly context: AttachmentsModuleContext<Context, Handler>) {} attachment<T extends Content>( content: MaybePromise<T>, options: Options, ): Promise<string | undefined> { if (typeof options.handler === 'string' && !this.context.handlers[options.handler]) { throw new AllureRuntimeError(`Unknown attachment handler: ${options.handler}`); } return thruMaybePromise(content, this.#handleAttachment(options)); } createAttachment<T extends Content>( function_: Function_<MaybePromise<T>>, options: Options, ): typeof function_ { return hijackFunction(function_, this.#handleAttachment(options)); } protected abstract _createMimeContext(name: string, content: Content): MIMEInfererContext; protected abstract _createAttachmentContext(context: AttachmentContext): Context; #handleAttachment(userOptions: Options) { const formatName = this.context.handlebars.compile(userOptions.name ?? 'untitled'); return (userContent: Content, arguments_?: unknown[]): Promise<string | undefined> => { const handler = this.#resolveHandler(userOptions); const name = formatName(arguments_); const mimeContext = this._createMimeContext(name, userContent); const mimeType = userOptions.mimeType ?? this.context.inferMimeType(mimeContext) ?? 'application/octet-stream'; const context = this._createAttachmentContext({ name, mimeType, outDir: this.context.outDir, sourcePath: mimeContext.sourcePath, content: mimeContext.content, size: userOptions.size, }); const pushAttachment = this.#schedulePushAttachment(context); return this.context.waitFor( Promise.resolve() .then(() => handler(context)) .then(pushAttachment), ); }; } #resolveHandler(options: Options): Handler { const handler = (options.handler ?? 'default') as string | Handler; return typeof handler === 'function' ? handler : this.context.handlers[handler]; } #schedulePushAttachment( context: Context, ): (result: AttachmentHandlerResult) => string | undefined { const metadata = this.context.metadata.$bind(); return (result) => { if (result) { const isObject = typeof result === 'object'; const source = isObject ? result.source : result; const size = isObject ? result.size : context.size; metadata.push('attachments', [ { name: context.name, type: context.mimeType, source, ...(size != null && { size }), }, ]); return source; } return; }; } } export class FileAttachmentsModule extends AttachmentsModule< FileAttachmentContext, string, FileAttachmentHandler, FileAttachmentOptions > { static create(context: AllureRuntimeContext): FileAttachmentsModule { return new FileAttachmentsModule({ get handlers() { return context.fileAttachmentHandlers; }, get handlebars() { return context.handlebars; }, get inferMimeType() { return context.inferMimeType; }, get metadata() { return context.getCurrentMetadata(); }, get outDir() { const config = context.getReporterConfig(); return path.join(config.resultsDir, config.attachments.subDir ?? ''); }, waitFor: context.enqueueTask, }); } protected _createMimeContext(_name: string, sourcePath: string) { return { sourcePath }; } protected _createAttachmentContext(context: AttachmentContext) { // somewhat fragile - relying here on _createMimeContext output return { sourcePath: context.sourcePath!, ...context }; } } export class ContentAttachmentsModule extends AttachmentsModule< ContentAttachmentContext, AttachmentContent, ContentAttachmentHandler, ContentAttachmentOptions > { static create(context: AllureRuntimeContext): ContentAttachmentsModule { return new ContentAttachmentsModule({ get handlers() { return context.contentAttachmentHandlers; }, get handlebars() { return context.handlebars; }, get inferMimeType() { return context.inferMimeType; }, get metadata() { return context.getCurrentMetadata(); }, get outDir() { const config = context.getReporterConfig(); return path.join(config.resultsDir, config.attachments.subDir ?? ''); }, waitFor: context.enqueueTask, }); } protected _createMimeContext(name: string, content: AttachmentContent) { let value = content; if (typeof content !== 'string' && !Buffer.isBuffer(content) && !ArrayBuffer.isView(content)) { value = util.inspect(content); } return { sourcePath: name, content: value }; } protected _createAttachmentContext(context: AttachmentContext) { // somewhat fragile - relying here on _createMimeContext output return { content: context.content!, ...context }; } }