From 79b7ea512984374042fd96a76464d1226ed4932e Mon Sep 17 00:00:00 2001 From: Jupegarnica Date: Wed, 29 Nov 2023 12:42:33 +0100 Subject: [PATCH] Remove unnecessary files and code --- {src => OLD/src}/assertResponse.ts | 0 OLD/src/cli.ts | 239 ++++++++++++ {src => OLD/src}/command.ts | 0 {src => OLD/src}/fetchBlock.ts | 0 {src => OLD/src}/files.ts | 0 {src => OLD/src}/help.ts | 0 {src => OLD/src}/highlight.ts | 0 {src => OLD/src}/logger.ts | 0 {src => OLD/src}/parser.ts | 0 {src => OLD/src}/print.ts | 0 OLD/src/runner.ts | 419 +++++++++++++++++++++ OLD/src/types.ts | 201 ++++++++++ OLD/src/version.ts | 1 + {test => OLD/test}/assertResponse.test.ts | 0 {test => OLD/test}/command.test.ts | 0 {test => OLD/test}/e2e.test.ts | 0 {test => OLD/test}/fetchBlock.test.ts | 0 {test => OLD/test}/files.test.ts | 0 {test => OLD/test}/parser.test.ts | 0 {test => OLD/test}/runner.test.ts | 0 src/cli.ts | 427 +++++++++++----------- src/display.ts | 28 ++ src/runner.ts | 1 - src/types.ts | 189 +--------- 24 files changed, 1106 insertions(+), 399 deletions(-) rename {src => OLD/src}/assertResponse.ts (100%) create mode 100644 OLD/src/cli.ts rename {src => OLD/src}/command.ts (100%) rename {src => OLD/src}/fetchBlock.ts (100%) rename {src => OLD/src}/files.ts (100%) rename {src => OLD/src}/help.ts (100%) rename {src => OLD/src}/highlight.ts (100%) rename {src => OLD/src}/logger.ts (100%) rename {src => OLD/src}/parser.ts (100%) rename {src => OLD/src}/print.ts (100%) create mode 100644 OLD/src/runner.ts create mode 100644 OLD/src/types.ts create mode 100644 OLD/src/version.ts rename {test => OLD/test}/assertResponse.test.ts (100%) rename {test => OLD/test}/command.test.ts (100%) rename {test => OLD/test}/e2e.test.ts (100%) rename {test => OLD/test}/fetchBlock.test.ts (100%) rename {test => OLD/test}/files.test.ts (100%) rename {test => OLD/test}/parser.test.ts (100%) rename {test => OLD/test}/runner.test.ts (100%) create mode 100644 src/display.ts diff --git a/src/assertResponse.ts b/OLD/src/assertResponse.ts similarity index 100% rename from src/assertResponse.ts rename to OLD/src/assertResponse.ts diff --git a/OLD/src/cli.ts b/OLD/src/cli.ts new file mode 100644 index 0000000..d070a77 --- /dev/null +++ b/OLD/src/cli.ts @@ -0,0 +1,239 @@ +import { type Args, parse } from "https://deno.land/std@0.178.0/flags/mod.ts"; +import type { Meta } from "./types.ts"; +import * as fmt from "https://deno.land/std@0.178.0/fmt/colors.ts"; +import { relative } from "https://deno.land/std@0.178.0/path/posix.ts"; +import { globsToFilePaths } from "./files.ts"; +import { load } from "https://deno.land/std@0.178.0/dotenv/mod.ts"; +import { runner } from "./runner.ts"; +import { DISPLAYS, getDisplayIndex } from "./print.ts"; +import { help, readme } from "./help.ts"; + +const mustExit = !Deno.env.get("TEPI_NOT_EXIT"); +function exit(code: number) { + mustExit && Deno.exit(code); +} + +Deno.addSignalListener("SIGINT", () => { + console.info(fmt.yellow('\nForcefully exiting with code 143 (SIGINT)')); + exit(143); +}); + +if (import.meta.main) { + await cli(); +} + +export async function cli() { + const options = { + default: { + display: "default", + help: false, + }, + collect: ["watch", "envFile", "watch-no-clear"], + boolean: ["help", "failFast", "noColor", "upgrade", "noAnimation", "readme"], + string: ["display", "envFile"], + + alias: { + h: "help", + w: "watch", + t: "timeout", + f: "failFast", + d: "display", + e: "envFile", + r: "readme", + envFile: "env-file", + noColor: "no-color", + watchNoClear: "watch-no-clear", + noAnimation: "no-animation", + failFast: "fail-fast", + }, + }; + const args: Args = parse(Deno.args, options); + + // --no-color + ///////////// + if (args.noColor) { + fmt.setColorEnabled(false); + } + // --upgrade + ///////////// + if (args.upgrade) { + const { code } = await new Deno.Command(Deno.execPath(), { + args: + "install --unstable -A --reload -f -n tepi https://tepi.deno.dev/src/cli.ts?upgrade=true" + .split(" "), + stdout: "inherit", + stderr: "inherit", + }).output(); + exit(code); + } + + // --version + ///////////// + if (args.version) { + // fetch VERSION file + const { VERSION } = await import("./version.ts") + console.log(VERSION); + exit(0); + return; + } + + // --help + ///////////// + if (args.help) { + help(); + return; + } + + if (args.readme) { + readme(); + return; + } + + // --display + ///////////// + + if (args.display === "") { + args.display = "full"; + } + + const defaultMeta: Meta = { + timeout: 0, + display: args.display as string, + }; + if (getDisplayIndex(defaultMeta) === Infinity) { + console.error( + fmt.brightRed( + `Invalid display mode ${args.display}\n Must be one of: ${DISPLAYS.map((t) => fmt.bold(t)).join(", ") + }`, + ), + ); + exit(1); + } + + // --no-animation + ///////////// + if (args.noAnimation) { + defaultMeta._noAnimation = true; + } + + // --env-file + ///////////// + const keysLoaded = new Set(); + const envFiles = new Set(); + for (const path of args.envFile) { + const vars = await load({ + export: true, + envPath: path, + allowEmptyValues: true, + }); + for (const key in vars) { + keysLoaded.add(key); + envFiles.add(path); + } + } + if (keysLoaded.size && getDisplayIndex(defaultMeta) > 0) { + console.info( + fmt.gray( + `Loaded ${keysLoaded.size} environment variables from: ${Array.from(envFiles).join(", ") + }`, + ), + ); + } + + // resolves globs to file paths and skips globs that have line specs + ///////////// + const globs: string[] = args._.length ? args._ as string[] : ["**/*.http"]; + + const filePathsToRun = await globsToFilePaths(globs); + + // runner + ///////////// + let { exitCode, onlyMode } = await runner( + filePathsToRun, + defaultMeta, + args.failFast, + ); + + // warn only mode + ///////////// + + // TODO FIX ABSOLUTE PATHS + // TODO FIX RELATIVE PATHS FROM AUTOSIDE INSTALL FOLDER + if (onlyMode.size) { + if (getDisplayIndex(defaultMeta) > 0) { + console.warn( + fmt.yellow( + `\n${fmt.bgYellow(fmt.bold(" ONLY MODE "))} ${onlyMode.size} ${onlyMode.size === 1 ? "test" : "tests" + } are in "only" mode.`, + ), + ); + if (!exitCode) { + console.error( + fmt.red( + `\nExited with code 1 because the ${fmt.bold('"only"')} option was used at ${[...onlyMode].join(", ") + }`, + ), + ); + } + } + exitCode ||= 1; + } + + // --watch + ///////////// + if (args.watch || args["watch-no-clear"]) { + const watch = args.watch || args["watch-no-clear"]; + const filePathsToJustWatch = await globsToFilePaths( + watch.filter((i: boolean | string) => typeof i === "string"), + ); + const noClear = !!args["watch-no-clear"]; + watchAndRun(filePathsToRun, filePathsToJustWatch, defaultMeta, noClear) + .catch( + console.error, + ); + } else { + exit(exitCode); + } +} + +function logWatchingPaths(filePaths: string[], filePathsToJustWatch: string[]) { + console.info(fmt.dim("\nWatching and running tests from:")); + filePaths.map((_filePath) => relative(Deno.cwd(), _filePath)).forEach(( + _filePath, + ) => console.info(fmt.cyan(` ${_filePath}`))); + if (filePathsToJustWatch.length) { + console.info(fmt.dim("\nRerun when changes from:")); + filePathsToJustWatch.map((_filePath) => relative(Deno.cwd(), _filePath)) + .forEach((_filePath) => console.info(fmt.cyan(` ${_filePath}`))); + } +} + +async function watchAndRun( + filePaths: string[], + filePathsToJustWatch: string[], + defaultMeta: Meta, + noClear: boolean, +) { + const allFilePaths = filePaths.concat(filePathsToJustWatch); + const watcher = Deno.watchFs(allFilePaths); + logWatchingPaths(filePaths, filePathsToJustWatch); + + for await (const event of watcher) { + if (event.kind === "access") { + noClear || console.clear(); + await runner(filePaths, defaultMeta); + // logWatchingPaths(filePaths, filePathsToJustWatch); + if (event.paths.some((path) => filePathsToJustWatch.includes(path))) { + // run all + noClear || console.clear(); + await runner(filePaths, defaultMeta); + logWatchingPaths(filePaths, filePathsToJustWatch); + } else { + // run just this file + noClear || console.clear(); + await runner(event.paths, defaultMeta); + logWatchingPaths(filePaths, filePathsToJustWatch); + } + } + } +} diff --git a/src/command.ts b/OLD/src/command.ts similarity index 100% rename from src/command.ts rename to OLD/src/command.ts diff --git a/src/fetchBlock.ts b/OLD/src/fetchBlock.ts similarity index 100% rename from src/fetchBlock.ts rename to OLD/src/fetchBlock.ts diff --git a/src/files.ts b/OLD/src/files.ts similarity index 100% rename from src/files.ts rename to OLD/src/files.ts diff --git a/src/help.ts b/OLD/src/help.ts similarity index 100% rename from src/help.ts rename to OLD/src/help.ts diff --git a/src/highlight.ts b/OLD/src/highlight.ts similarity index 100% rename from src/highlight.ts rename to OLD/src/highlight.ts diff --git a/src/logger.ts b/OLD/src/logger.ts similarity index 100% rename from src/logger.ts rename to OLD/src/logger.ts diff --git a/src/parser.ts b/OLD/src/parser.ts similarity index 100% rename from src/parser.ts rename to OLD/src/parser.ts diff --git a/src/print.ts b/OLD/src/print.ts similarity index 100% rename from src/print.ts rename to OLD/src/print.ts diff --git a/OLD/src/runner.ts b/OLD/src/runner.ts new file mode 100644 index 0000000..4efbf51 --- /dev/null +++ b/OLD/src/runner.ts @@ -0,0 +1,419 @@ +import { filePathsToFiles } from "./files.ts"; +import { Block, File, GlobalData, Meta } from "./types.ts"; +import { consumeBodies, fetchBlock } from "./fetchBlock.ts"; +import { assertResponse } from "./assertResponse.ts"; +import * as fmt from "https://deno.land/std@0.178.0/fmt/colors.ts"; +import { + dirname, + isAbsolute, + relative, + resolve, +} from "https://deno.land/std@0.178.0/path/posix.ts"; +import { + createBlockSpinner, + getDisplayIndex, + logPath, + printBlock, + printErrorsSummary, +} from "./print.ts"; +import { ms } from "https://deno.land/x/ms@v0.1.0/ms.ts"; +import { + parseMetaFromText, + parseRequestFromText, + parseResponseFromText, +} from "./parser.ts"; +import * as assertions from "https://deno.land/std@0.178.0/testing/asserts.ts"; +import { executeCommand } from "./command.ts"; + +export async function runner( + filePaths: string[], + defaultMeta: Meta, + failFast = false, +): Promise< + { + files: File[]; + exitCode: number; + onlyMode: Set; + blocksDone: Set; + } +> { + let successfulBlocks = 0; + let failedBlocks = 0; + let ignoredBlocks = 0; + const blocksDone = new Set(); + const startGlobalTime = Date.now(); + + const onlyMode = new Set(); + const mustBeImported = new Set(); + + const files: File[] = await filePathsToFiles(filePaths); + + const globalData: GlobalData = { + meta: { + ...defaultMeta, + get ignore() { + return undefined; + // do not save ignore in global meta + }, + get only() { + // do not save only in global meta + return undefined; + }, + get id() { + // do not save id in global meta + return undefined; + }, + get description() { + // do not save description in global meta + return undefined; + } + }, + _files: files, + _blocksDone: {}, + _blocksAlreadyReferenced: new Set(), + }; + + // parse all metadata first + + const allPathFilesImported = new Set(); + try { + await processMetadata( + files, + globalData, + onlyMode, + mustBeImported, + blocksDone, + allPathFilesImported, + ); + } catch (error) { + console.error(`Error while parsing metadata:`); + console.error(error.message); + return { files, exitCode: 1, onlyMode, blocksDone }; + } + + + + + + if (onlyMode.size) { + for (const file of files) { + for (const block of file.blocks) { + if (!block.meta.only) { + block.meta.ignore = true; + } else { + block.meta.ignore = undefined; + } + } + } + } + + // look for all blocks needed + const allIdsNeeded = new Set(); + for (const file of files) { + for (const block of file.blocks) { + if (block.meta.needs) { + allIdsNeeded.add(block.meta.needs); + } + } + } + const allBlockNeeded = new Map(); + for (const file of files) { + for (const block of file.blocks) { + if (allIdsNeeded.has(block.meta.id)) { + allBlockNeeded.set(block.meta.id, block); + block.meta.ignore = false; + } + } + } + + for (const file of files) { + const relativePath = file.relativePath || ""; + const path = fmt.gray(`running ${relativePath} `); + const displayIndex = getDisplayIndex(defaultMeta); + const pathSpinner = logPath(path, displayIndex, defaultMeta._noAnimation); + let _isFirstBlock = true; + for (const block of file.blocks) { + block.meta._relativeFilePath = relativePath; + block.meta._isFirstBlock = _isFirstBlock; + if (_isFirstBlock) { + _isFirstBlock = false; + } + + await runBlock(block, globalData, relativePath, blocksDone, allBlockNeeded); + if (block.meta._isIgnoredBlock) { + ignoredBlocks++; + } + if (block.meta._isFailedBlock) { + failedBlocks++; + } + if (block.meta._isSuccessfulBlock) { + successfulBlocks++; + } + + if (failFast && failedBlocks) { + if (getDisplayIndex(defaultMeta) !== 0) { + printErrorsSummary(blocksDone); + } + const status = block.actualResponse?.status || 1; + console.error(fmt.red(`\nFAIL FAST: exiting with status ${status}`)); + return { + files, + exitCode: status, + onlyMode, + blocksDone, + }; + } + } + pathSpinner?.stop(); + pathSpinner?.clear(); + } + + const totalBlockRun = successfulBlocks + failedBlocks; + const exitCode = failedBlocks > 0 + ? failedBlocks + : totalBlockRun === 0 + ? 1 + : 0; + + if (getDisplayIndex(defaultMeta) !== 0) { + printErrorsSummary(blocksDone); + + const statusText = exitCode > 0 + ? fmt.bgRed(" FAIL ") + : fmt.bgBrightGreen(" PASS "); + + const totalBlocks = successfulBlocks + failedBlocks + ignoredBlocks; + const elapsedGlobalTime = Date.now() - startGlobalTime; + const prettyGlobalTime = fmt.dim(`(${ms(elapsedGlobalTime)})`); + console.info(); + console.info( + fmt.bold(`${statusText}`), + `${fmt.white(String(totalBlocks))} tests, ${fmt.green(String(successfulBlocks)) + } passed, ${fmt.red(String(failedBlocks))} failed, ${fmt.yellow(String(ignoredBlocks)) + } ignored ${prettyGlobalTime}`, + ); + } + globalData._blocksDone = {}; // clean up blocks referenced + return { files, exitCode, onlyMode, blocksDone }; +} + +function addToDone(blocksDone: Set, block: Block) { + if (blocksDone.has(block)) { + throw new Error("Block already done: " + block.description); + } + if (block.meta._isDoneBlock) { + throw new Error("Block already _isDoneBlock"); + } + block.meta._isDoneBlock = true; + blocksDone.add(block); +} + +async function runBlock( + block: Block, + globalData: GlobalData, + currentFilePath: string, + blocksDone: Set, + allBlockNeeded: Map, +): Promise> { + if (blocksDone.has(block)) { + return blocksDone; + } + const spinner = createBlockSpinner(block, currentFilePath, globalData.meta); + + try { + if (block.meta.needs && !block.meta.ignore) { + const blockNeeded = allBlockNeeded.get(block.meta.needs) + if (!blockNeeded) { + throw new Error(`Block needed not found: ${block.meta.needs}`); + } + if (!blockNeeded.meta._isDoneBlock && globalData._blocksAlreadyReferenced.has(blockNeeded)) { + // Evict infinity loop + throw new Error( + `Infinite loop looking for needed blocks -> ${block.description} needs ${block.meta.needs}`, + ); + } + globalData._blocksAlreadyReferenced.add(blockNeeded); + + await runBlock( + blockNeeded, + globalData, + currentFilePath, + blocksDone, + allBlockNeeded, + ); + + if (blocksDone.has(block)) { + return blocksDone; + } + } + + + block.meta = { + ...globalData.meta, + ...block.meta, + }; + spinner.start(); + try { + block.request = await parseRequestFromText(block, { + ...globalData._blocksDone, + ...block, + ...assertions, + }); + } catch (error) { + if (block.meta.ignore) { + // should not fail if ignored + } else { + error.message = `Error while parsing request: ${error.message}`; + throw error; + } + } + + spinner.update(); + + if (block.meta._isFirstBlock && !block.request) { + globalData.meta = { ...globalData.meta, ...block.meta }; + } + + if (block.meta.ignore) { + block.meta._isIgnoredBlock = true; + addToDone(blocksDone, block); + spinner.ignore(); + return blocksDone; + } + + if (block.meta.command) { + spinner.clear(); + await executeCommand(block); + spinner.start(); + + } + + if (!block.request) { + if (block.meta._isFirstBlock) { + spinner.clear(); + } else { + spinner.empty(); + } + block.meta._isEmptyBlock = true; + addToDone(blocksDone, block); + return blocksDone; + } + + if (block.error) { + throw block.error; + } + + await fetchBlock(block); + + try { + block.expectedResponse = await parseResponseFromText( + block.text, + { + ...globalData._blocksDone, + ...block, + ...assertions, + body: await block.actualResponse?.getBody(), + // body: block.body, + response: block.response, + }, + ); + } catch (error) { + error.message = `Error while parsing response: ${error.message}`; + throw error; + } + + if (block.expectedResponse) { + await assertResponse(block); + } + + block.meta._isSuccessfulBlock = true; + spinner.pass(); + addToDone(blocksDone, block); + return blocksDone; + } catch (error) { + block.error = error; + block.meta._isFailedBlock = true; + spinner.fail(); + addToDone(blocksDone, block); + return blocksDone; + } finally { + await printBlock(block); + await consumeBodies(block); + + if (block.meta.id) { + const name = block.meta.id as string; + block.body = await block.actualResponse?.getBody(); + globalData._blocksDone[name] = block; + } + } +} + +async function processMetadata( + files: File[], + globalData: GlobalData, + onlyMode: Set, + mustBeImported: Set, + blocksDone: Set, + allPathFilesImported: Set, +) { + for (const file of files) { + if (allPathFilesImported.has(file.path)) { + // evict infinity loop + throw new Error(`Infinite loop looking for imports -> ${file.path}`); + } + file.relativePath = "./" + relative(Deno.cwd(), file.path); + for (const block of file.blocks) { + try { + const meta = await parseMetaFromText(block.text, { + ...globalData, + ...block, + ...assertions, + }); + block.meta._relativeFilePath ??= file.relativePath; + block.meta = { + ...globalData.meta, + ...block.meta, + ...meta, + }; + if (block.meta.only) { + onlyMode.add( + `${block.meta._relativeFilePath}:${block.meta._startLine}`, + ); + } + if (block.meta.import) { + if (isAbsolute(block.meta.import)) { + mustBeImported.add(block.meta.import); + } else { + mustBeImported.add(resolve(dirname(file.path), block.meta.import)); + } + } + } catch (error) { + error.message = `Error parsing metadata: ${error.message}`; + block.error = error; + block.meta._isFailedBlock = true; + } + } + allPathFilesImported.add(file.path); + } + // meta.import logic + // TODO support for import globs for example -> import: ./src/*.http ./test/*.http + if (mustBeImported.size > 0) { + const allAbsolutePaths = files.map((f) => f.path); + const needsImport = Array.from(mustBeImported).filter( + (path) => !allAbsolutePaths.includes(path), + ); + + const newFiles = await filePathsToFiles(needsImport); + const _mustBeImported = new Set(); + await processMetadata( + newFiles, + globalData, + onlyMode, + _mustBeImported, + blocksDone, + allPathFilesImported, + ); + files.unshift(...newFiles); + files.sort((file) => mustBeImported.has(file.path) ? -1 : 1); + } +} diff --git a/OLD/src/types.ts b/OLD/src/types.ts new file mode 100644 index 0000000..5094431 --- /dev/null +++ b/OLD/src/types.ts @@ -0,0 +1,201 @@ +import { extractBody } from "./fetchBlock.ts"; +// TODO make-synchronous? + +export class _Response extends Response { + bodyRaw?: BodyInit | null; + #bodyExtracted?: unknown; + + static fromResponse( + response: Response, + bodyRaw?: BodyInit | null, + ): _Response { + const _response = new _Response(response.body, response); + _response.bodyRaw = bodyRaw; + return _response; + } + constructor(body?: BodyInit | null | undefined, init?: ResponseInit) { + super(body, init); + this.bodyRaw = body; + } + async getBody(): Promise { + await extractBody(this); + return this.#bodyExtracted; + } + get bodyExtracted() { + return this.#bodyExtracted; + } + set bodyExtracted(value) { + this.#bodyExtracted = value; + } +} + +export class _Request extends Request { + bodyRaw?: BodyInit | null; + #bodyExtracted?: unknown; + constructor(input: RequestInfo, init?: RequestInit) { + super(input, init); + this.bodyRaw = init?.body; + } + async getBody(): Promise { + await extractBody(this); + return this.bodyExtracted; + } + get bodyExtracted() { + return this.#bodyExtracted; + } + set bodyExtracted(value) { + this.#bodyExtracted = value; + } +} + +export type Meta = { + _elapsedTime?: number | string; + _startLine?: number; + _endLine?: number; + _filePath?: string; + _relativeFilePath?: string; + _noAnimation?: boolean; + + _isEmptyBlock?: boolean; + _isDoneBlock?: boolean; + _isSuccessfulBlock?: boolean; + _isFailedBlock?: boolean; + _isIgnoredBlock?: boolean; + _errorDisplayed?: boolean; + + // deno-lint-ignore no-explicit-any + [key: string]: any; +}; + +export type BodyExtracted = { body: unknown; contentType: string }; + +export class Block { + text: string; + meta: Meta; + request?: _Request; + expectedResponse?: _Response; + actualResponse?: _Response; + error?: Error; + body?: unknown; + // #getBodySync: () => unknown; + // #getBodyAsync:() => Promise; + + constructor(obj: Partial = {}) { + this.text = obj.text || ""; + this.meta = obj.meta || {}; + this.expectedResponse = obj.expectedResponse; + this.actualResponse = obj.actualResponse; + // this.#getBodyAsync = this.actualResponse?.getBody || ( function(): Promise { return Promise.resolve()}) + // this.#getBodySync = makeSynchronous(this.#getBodyAsync) + } + // get body(): unknown { + // return this.#getBodySync(); + // } + get description(): string { + if (this.meta.description) { + return String(this.meta.description); + } + if (this.meta.id) { + return String(this.meta.id); + } + if (this.request) { + return `${this.request.method} ${this.request.url}`; + } + return `${this.meta._relativeFilePath}:${this.meta._startLine}`; + } + get response() { + return this.actualResponse; + } +} + +export type File = { + path: string; + relativePath?: string; + blocks: Block[]; +}; + +export type GlobalData = { + meta: Meta; + _files: File[]; + _blocksAlreadyReferenced: Set; + _blocksDone: { + [key: string]: Block; + }; +}; + +// FETCH VALID HTTP METHODS + +export const httpMethods = [ + "GET", + "HEAD", + "POST", + "PUT", + "DELETE", + "CONNECT", + "OPTIONS", + "TRACE", + "PATCH", +]; + +export const mimesToArrayBuffer = [ + "image/png", + "image/jpeg", + "image/gif", + "image/webp", + "image/bmp", + "image/tiff", +]; + +export const mimesToText = [ + "text/plain", + "text/html", + "text/css", + "text/javascript", + "text/xml", + "application/xml", + "text/markdown", + "text/csv", + "text/yaml", + "image/svg+xml", + "image/svg", + "application/x-yaml", + "application/yaml", +]; + +export const mimesToJSON = [ + "application/json", + "application/ld+json", + "application/manifest+json", + "application/schema+json", + "application/vnd.api+json", + "application/vnd.geo+json", +]; + +export const mimesToBlob = [ + "application/octet-stream", + "application/pdf", + "application/zip", + "application/docx", + "application/x-docx", + "application/x-rar-compressed", + "application/x-7z-compressed", + "application/x-tar", + "application/x-gzip", + "application/x-bzip2", + "application/x-xz", + "application/x-lzma", + "application/x-lzip", + "application/x-lzop", + "application/x-snappy-framed", + "application/x-msdownload", + "application/x-msi", + "application/x-ms-shortcut", + "application/x-ms-wim", + "application/x-ms-xbap", + "application/x-msaccess", +]; + +export const mimesToFormData = [ + "multipart/form-data", + "application/x-www-form-urlencoded", +]; diff --git a/OLD/src/version.ts b/OLD/src/version.ts new file mode 100644 index 0000000..b9ccd26 --- /dev/null +++ b/OLD/src/version.ts @@ -0,0 +1 @@ +export const VERSION='1.0.66' diff --git a/test/assertResponse.test.ts b/OLD/test/assertResponse.test.ts similarity index 100% rename from test/assertResponse.test.ts rename to OLD/test/assertResponse.test.ts diff --git a/test/command.test.ts b/OLD/test/command.test.ts similarity index 100% rename from test/command.test.ts rename to OLD/test/command.test.ts diff --git a/test/e2e.test.ts b/OLD/test/e2e.test.ts similarity index 100% rename from test/e2e.test.ts rename to OLD/test/e2e.test.ts diff --git a/test/fetchBlock.test.ts b/OLD/test/fetchBlock.test.ts similarity index 100% rename from test/fetchBlock.test.ts rename to OLD/test/fetchBlock.test.ts diff --git a/test/files.test.ts b/OLD/test/files.test.ts similarity index 100% rename from test/files.test.ts rename to OLD/test/files.test.ts diff --git a/test/parser.test.ts b/OLD/test/parser.test.ts similarity index 100% rename from test/parser.test.ts rename to OLD/test/parser.test.ts diff --git a/test/runner.test.ts b/OLD/test/runner.test.ts similarity index 100% rename from test/runner.test.ts rename to OLD/test/runner.test.ts diff --git a/src/cli.ts b/src/cli.ts index d070a77..a76d1ec 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,239 +1,234 @@ +// import { relative } from "https://deno.land/std@0.178.0/path/posix.ts"; +// import { globsToFilePaths } from "./files.ts"; +// import { runner } from "./runner.ts"; +// import { help, readme } from "./help.ts"; + +import { load as loadEnv } from "https://deno.land/std@0.178.0/dotenv/mod.ts"; +import { DISPLAYS, getDisplayIndex } from "./display.ts"; import { type Args, parse } from "https://deno.land/std@0.178.0/flags/mod.ts"; -import type { Meta } from "./types.ts"; +import type { Message, Meta } from "./types.ts"; import * as fmt from "https://deno.land/std@0.178.0/fmt/colors.ts"; -import { relative } from "https://deno.land/std@0.178.0/path/posix.ts"; -import { globsToFilePaths } from "./files.ts"; -import { load } from "https://deno.land/std@0.178.0/dotenv/mod.ts"; import { runner } from "./runner.ts"; -import { DISPLAYS, getDisplayIndex } from "./print.ts"; -import { help, readme } from "./help.ts"; const mustExit = !Deno.env.get("TEPI_NOT_EXIT"); function exit(code: number) { - mustExit && Deno.exit(code); + mustExit && Deno.exit(code); } Deno.addSignalListener("SIGINT", () => { - console.info(fmt.yellow('\nForcefully exiting with code 143 (SIGINT)')); - exit(143); + console.info(fmt.yellow('\nForcefully exiting with code 143 (SIGINT)')); + exit(143); }); if (import.meta.main) { - await cli(); + await cli(); } export async function cli() { - const options = { - default: { - display: "default", - help: false, - }, - collect: ["watch", "envFile", "watch-no-clear"], - boolean: ["help", "failFast", "noColor", "upgrade", "noAnimation", "readme"], - string: ["display", "envFile"], - - alias: { - h: "help", - w: "watch", - t: "timeout", - f: "failFast", - d: "display", - e: "envFile", - r: "readme", - envFile: "env-file", - noColor: "no-color", - watchNoClear: "watch-no-clear", - noAnimation: "no-animation", - failFast: "fail-fast", - }, - }; - const args: Args = parse(Deno.args, options); - - // --no-color - ///////////// - if (args.noColor) { - fmt.setColorEnabled(false); - } - // --upgrade - ///////////// - if (args.upgrade) { - const { code } = await new Deno.Command(Deno.execPath(), { - args: - "install --unstable -A --reload -f -n tepi https://tepi.deno.dev/src/cli.ts?upgrade=true" - .split(" "), - stdout: "inherit", - stderr: "inherit", - }).output(); - exit(code); - } - - // --version - ///////////// - if (args.version) { - // fetch VERSION file - const { VERSION } = await import("./version.ts") - console.log(VERSION); - exit(0); - return; - } - - // --help - ///////////// - if (args.help) { - help(); - return; - } - - if (args.readme) { - readme(); - return; - } - - // --display - ///////////// - - if (args.display === "") { - args.display = "full"; - } - - const defaultMeta: Meta = { - timeout: 0, - display: args.display as string, - }; - if (getDisplayIndex(defaultMeta) === Infinity) { - console.error( - fmt.brightRed( - `Invalid display mode ${args.display}\n Must be one of: ${DISPLAYS.map((t) => fmt.bold(t)).join(", ") - }`, - ), - ); - exit(1); - } - - // --no-animation - ///////////// - if (args.noAnimation) { - defaultMeta._noAnimation = true; - } - - // --env-file - ///////////// - const keysLoaded = new Set(); - const envFiles = new Set(); - for (const path of args.envFile) { - const vars = await load({ - export: true, - envPath: path, - allowEmptyValues: true, - }); - for (const key in vars) { - keysLoaded.add(key); - envFiles.add(path); + const messages: Message[] = [] + const options = { + default: { + display: "default", + help: false, + }, + collect: ["watch", "envFile", "watch-no-clear"], + boolean: ["help", "failFast", "noColor", "upgrade", "readme"], + string: ["display", "envFile"], + + alias: { + h: "help", + w: "watch", + t: "timeout", + f: "failFast", + d: "display", + e: "envFile", + r: "readme", + envFile: "env-file", + noColor: "no-color", + watchNoClear: "watch-no-clear", + failFast: "fail-fast", + }, + }; + const args: Args = parse(Deno.args, options); + + // --no-color + ///////////// + if (args.noColor) { + fmt.setColorEnabled(false); } - } - if (keysLoaded.size && getDisplayIndex(defaultMeta) > 0) { - console.info( - fmt.gray( - `Loaded ${keysLoaded.size} environment variables from: ${Array.from(envFiles).join(", ") - }`, - ), - ); - } - - // resolves globs to file paths and skips globs that have line specs - ///////////// - const globs: string[] = args._.length ? args._ as string[] : ["**/*.http"]; - - const filePathsToRun = await globsToFilePaths(globs); - - // runner - ///////////// - let { exitCode, onlyMode } = await runner( - filePathsToRun, - defaultMeta, - args.failFast, - ); - - // warn only mode - ///////////// - - // TODO FIX ABSOLUTE PATHS - // TODO FIX RELATIVE PATHS FROM AUTOSIDE INSTALL FOLDER - if (onlyMode.size) { - if (getDisplayIndex(defaultMeta) > 0) { - console.warn( - fmt.yellow( - `\n${fmt.bgYellow(fmt.bold(" ONLY MODE "))} ${onlyMode.size} ${onlyMode.size === 1 ? "test" : "tests" - } are in "only" mode.`, - ), - ); - if (!exitCode) { + // --upgrade + ///////////// + if (args.upgrade) { + const { code } = await new Deno.Command(Deno.execPath(), { + args: + "install --unstable -A --reload -f -n tepi https://tepi.deno.dev/src/cli.ts?upgrade=true" + .split(" "), + stdout: "inherit", + stderr: "inherit", + }).output(); + exit(code); + } + + // --version + ///////////// + if (args.version) { + // fetch VERSION file + const { VERSION } = await import("./version.ts") + console.log(VERSION); + exit(0); + return; + } + + // --help + ///////////// + if (args.help) { + // TODO help(); + + return; + } + + if (args.readme) { + // TODO readme(); + return; + } + + // --display + ///////////// + + if (args.display === "") { + args.display = "full"; + } + + const defaultMeta: Meta = { + timeout: 0, + display: args.display as string, + }; + + if (getDisplayIndex(defaultMeta) === Infinity) { console.error( - fmt.red( - `\nExited with code 1 because the ${fmt.bold('"only"')} option was used at ${[...onlyMode].join(", ") - }`, - ), + fmt.brightRed( + `Invalid display mode ${args.display}\n Must be one of: ${DISPLAYS.map((t: string) => fmt.bold(t)).join(", ") + }`, + ), ); - } + exit(1); } - exitCode ||= 1; - } - - // --watch - ///////////// - if (args.watch || args["watch-no-clear"]) { - const watch = args.watch || args["watch-no-clear"]; - const filePathsToJustWatch = await globsToFilePaths( - watch.filter((i: boolean | string) => typeof i === "string"), - ); - const noClear = !!args["watch-no-clear"]; - watchAndRun(filePathsToRun, filePathsToJustWatch, defaultMeta, noClear) - .catch( - console.error, - ); - } else { - exit(exitCode); - } -} -function logWatchingPaths(filePaths: string[], filePathsToJustWatch: string[]) { - console.info(fmt.dim("\nWatching and running tests from:")); - filePaths.map((_filePath) => relative(Deno.cwd(), _filePath)).forEach(( - _filePath, - ) => console.info(fmt.cyan(` ${_filePath}`))); - if (filePathsToJustWatch.length) { - console.info(fmt.dim("\nRerun when changes from:")); - filePathsToJustWatch.map((_filePath) => relative(Deno.cwd(), _filePath)) - .forEach((_filePath) => console.info(fmt.cyan(` ${_filePath}`))); - } -} -async function watchAndRun( - filePaths: string[], - filePathsToJustWatch: string[], - defaultMeta: Meta, - noClear: boolean, -) { - const allFilePaths = filePaths.concat(filePathsToJustWatch); - const watcher = Deno.watchFs(allFilePaths); - logWatchingPaths(filePaths, filePathsToJustWatch); - - for await (const event of watcher) { - if (event.kind === "access") { - noClear || console.clear(); - await runner(filePaths, defaultMeta); - // logWatchingPaths(filePaths, filePathsToJustWatch); - if (event.paths.some((path) => filePathsToJustWatch.includes(path))) { - // run all - noClear || console.clear(); - await runner(filePaths, defaultMeta); - logWatchingPaths(filePaths, filePathsToJustWatch); - } else { - // run just this file - noClear || console.clear(); - await runner(event.paths, defaultMeta); - logWatchingPaths(filePaths, filePathsToJustWatch); - } + + // --env-file + ///////////// + const keysLoaded = new Set(); + const envFiles = new Set(); + for (const path of args.envFile) { + const vars = await loadEnv({ + export: true, + envPath: path, + allowEmptyValues: true, + }); + for (const key in vars) { + keysLoaded.add(key); + envFiles.add(path); + } + } + + if (keysLoaded.size && getDisplayIndex(defaultMeta) > 0) { + + messages.push({ + type: "info", + message: `Loaded ${keysLoaded.size} environment variables from: ${Array.from(envFiles).join(", ")}`, + }); } - } + + + + // runner + ///////////// + let { exitCode, onlyMode } = await runner( + defaultMeta, + args, + ); + + // warn only mode + ///////////// + + // TODO FIX ABSOLUTE PATHS + // TODO FIX RELATIVE PATHS FROM AUTOSIDE INSTALL FOLDER + // if (onlyMode.size) { + // if (getDisplayIndex(defaultMeta) > 0) { + // console.warn( + // fmt.yellow( + // `\n${fmt.bgYellow(fmt.bold(" ONLY MODE "))} ${onlyMode.size} ${onlyMode.size === 1 ? "test" : "tests" + // } are in "only" mode.`, + // ), + // ); + // if (!exitCode) { + // console.error( + // fmt.red( + // `\nExited with code 1 because the ${fmt.bold('"only"')} option was used at ${[...onlyMode].join(", ") + // }`, + // ), + // ); + // } + // } + // exitCode ||= 1; + // } + + // --watch + ///////////// + // if (args.watch || args["watch-no-clear"]) { + // const watch = args.watch || args["watch-no-clear"]; + // const filePathsToJustWatch = await globsToFilePaths( + // watch.filter((i: boolean | string) => typeof i === "string"), + // ); + // const noClear = !!args["watch-no-clear"]; + // watchAndRun(filePathsToRun, filePathsToJustWatch, defaultMeta, noClear) + // .catch( + // console.error, + // ); + // } else { + // exit(exitCode); + // } } + +// function logWatchingPaths(filePaths: string[], filePathsToJustWatch: string[]) { +// console.info(fmt.dim("\nWatching and running tests from:")); +// filePaths.map((_filePath) => relative(Deno.cwd(), _filePath)).forEach(( +// _filePath, +// ) => console.info(fmt.cyan(` ${_filePath}`))); +// if (filePathsToJustWatch.length) { +// console.info(fmt.dim("\nRerun when changes from:")); +// filePathsToJustWatch.map((_filePath) => relative(Deno.cwd(), _filePath)) +// .forEach((_filePath) => console.info(fmt.cyan(` ${_filePath}`))); +// } +// } + +// async function watchAndRun( +// filePaths: string[], +// filePathsToJustWatch: string[], +// defaultMeta: Meta, +// noClear: boolean, +// ) { +// const allFilePaths = filePaths.concat(filePathsToJustWatch); +// const watcher = Deno.watchFs(allFilePaths); +// logWatchingPaths(filePaths, filePathsToJustWatch); + +// for await (const event of watcher) { +// if (event.kind === "access") { +// noClear || console.clear(); +// await runner(filePaths, defaultMeta); +// // logWatchingPaths(filePaths, filePathsToJustWatch); +// if (event.paths.some((path) => filePathsToJustWatch.includes(path))) { +// // run all +// noClear || console.clear(); +// await runner(filePaths, defaultMeta); +// logWatchingPaths(filePaths, filePathsToJustWatch); +// } else { +// // run just this file +// noClear || console.clear(); +// await runner(event.paths, defaultMeta); +// logWatchingPaths(filePaths, filePathsToJustWatch); +// } +// } +// } +// } diff --git a/src/display.ts b/src/display.ts new file mode 100644 index 0000000..0f6dd1f --- /dev/null +++ b/src/display.ts @@ -0,0 +1,28 @@ +import { Meta } from "./types.ts"; + +export const DISPLAYS = [ + "none", // 0 + "minimal", // 1 + "default", // 2 + "truncate", // 3 + "full", // 4 + "verbose", // 5 +]; + + +export function getDisplayIndex(meta: Meta): number { + const display = meta.display as string; + const index = DISPLAYS.indexOf(display); + if (index === -1) { + return Infinity; + } + return index; +} + +// function getDisplayIndexNamed(name: string): number { +// const index = DISPLAYS.indexOf(name); +// if (index === -1) { +// return Infinity; +// } +// return index; +// } diff --git a/src/runner.ts b/src/runner.ts index 4efbf51..e20b33c 100644 --- a/src/runner.ts +++ b/src/runner.ts @@ -26,7 +26,6 @@ import * as assertions from "https://deno.land/std@0.178.0/testing/asserts.ts"; import { executeCommand } from "./command.ts"; export async function runner( - filePaths: string[], defaultMeta: Meta, failFast = false, ): Promise< diff --git a/src/types.ts b/src/types.ts index 5094431..e0200e4 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,52 +1,10 @@ -import { extractBody } from "./fetchBlock.ts"; -// TODO make-synchronous? +export type Message = { + type: "error" | "warn" | "info" | "success"; + message: string; +}; -export class _Response extends Response { - bodyRaw?: BodyInit | null; - #bodyExtracted?: unknown; - static fromResponse( - response: Response, - bodyRaw?: BodyInit | null, - ): _Response { - const _response = new _Response(response.body, response); - _response.bodyRaw = bodyRaw; - return _response; - } - constructor(body?: BodyInit | null | undefined, init?: ResponseInit) { - super(body, init); - this.bodyRaw = body; - } - async getBody(): Promise { - await extractBody(this); - return this.#bodyExtracted; - } - get bodyExtracted() { - return this.#bodyExtracted; - } - set bodyExtracted(value) { - this.#bodyExtracted = value; - } -} -export class _Request extends Request { - bodyRaw?: BodyInit | null; - #bodyExtracted?: unknown; - constructor(input: RequestInfo, init?: RequestInit) { - super(input, init); - this.bodyRaw = init?.body; - } - async getBody(): Promise { - await extractBody(this); - return this.bodyExtracted; - } - get bodyExtracted() { - return this.#bodyExtracted; - } - set bodyExtracted(value) { - this.#bodyExtracted = value; - } -} export type Meta = { _elapsedTime?: number | string; @@ -63,139 +21,6 @@ export type Meta = { _isIgnoredBlock?: boolean; _errorDisplayed?: boolean; - // deno-lint-ignore no-explicit-any - [key: string]: any; -}; - -export type BodyExtracted = { body: unknown; contentType: string }; - -export class Block { - text: string; - meta: Meta; - request?: _Request; - expectedResponse?: _Response; - actualResponse?: _Response; - error?: Error; - body?: unknown; - // #getBodySync: () => unknown; - // #getBodyAsync:() => Promise; - - constructor(obj: Partial = {}) { - this.text = obj.text || ""; - this.meta = obj.meta || {}; - this.expectedResponse = obj.expectedResponse; - this.actualResponse = obj.actualResponse; - // this.#getBodyAsync = this.actualResponse?.getBody || ( function(): Promise { return Promise.resolve()}) - // this.#getBodySync = makeSynchronous(this.#getBodyAsync) - } - // get body(): unknown { - // return this.#getBodySync(); - // } - get description(): string { - if (this.meta.description) { - return String(this.meta.description); - } - if (this.meta.id) { - return String(this.meta.id); - } - if (this.request) { - return `${this.request.method} ${this.request.url}`; - } - return `${this.meta._relativeFilePath}:${this.meta._startLine}`; - } - get response() { - return this.actualResponse; - } -} - -export type File = { - path: string; - relativePath?: string; - blocks: Block[]; -}; - -export type GlobalData = { - meta: Meta; - _files: File[]; - _blocksAlreadyReferenced: Set; - _blocksDone: { - [key: string]: Block; - }; -}; - -// FETCH VALID HTTP METHODS - -export const httpMethods = [ - "GET", - "HEAD", - "POST", - "PUT", - "DELETE", - "CONNECT", - "OPTIONS", - "TRACE", - "PATCH", -]; - -export const mimesToArrayBuffer = [ - "image/png", - "image/jpeg", - "image/gif", - "image/webp", - "image/bmp", - "image/tiff", -]; - -export const mimesToText = [ - "text/plain", - "text/html", - "text/css", - "text/javascript", - "text/xml", - "application/xml", - "text/markdown", - "text/csv", - "text/yaml", - "image/svg+xml", - "image/svg", - "application/x-yaml", - "application/yaml", -]; - -export const mimesToJSON = [ - "application/json", - "application/ld+json", - "application/manifest+json", - "application/schema+json", - "application/vnd.api+json", - "application/vnd.geo+json", -]; - -export const mimesToBlob = [ - "application/octet-stream", - "application/pdf", - "application/zip", - "application/docx", - "application/x-docx", - "application/x-rar-compressed", - "application/x-7z-compressed", - "application/x-tar", - "application/x-gzip", - "application/x-bzip2", - "application/x-xz", - "application/x-lzma", - "application/x-lzip", - "application/x-lzop", - "application/x-snappy-framed", - "application/x-msdownload", - "application/x-msi", - "application/x-ms-shortcut", - "application/x-ms-wim", - "application/x-ms-xbap", - "application/x-msaccess", -]; - -export const mimesToFormData = [ - "multipart/form-data", - "application/x-www-form-urlencoded", -]; + // [key: string]: any; + [key: string]: unknown; +}; \ No newline at end of file