From 641d3de251f064139717649766de82c4d343adb4 Mon Sep 17 00:00:00 2001 From: j3lte Date: Tue, 21 Nov 2023 19:36:17 +0100 Subject: [PATCH] Let's do some automated testing --- .github/workflows/test.yml | 8 +- deno.json | 3 +- deno.lock | 90 +------- dev_deps.ts | 19 +- mocks/fetch.ts | 1 - src/deno/Pastebin.ts | 6 + src/lib/Pastebin.ts | 92 +++++++-- src/lib/interfaces.ts | 7 +- test/Pastebin.test.ts | 413 +++++++++++++++++++++++++++++++++++++ 9 files changed, 512 insertions(+), 127 deletions(-) delete mode 100644 mocks/fetch.ts create mode 100644 test/Pastebin.test.ts diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 30df4a6..087eb6a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -23,11 +23,11 @@ jobs: - name: Check linting run: deno lint - # - name: Tests - # run: deno task test + - name: Tests + run: deno task test - # - name: Coverage - # run: deno task coverage + - name: Coverage + run: deno task coverage # Disabled for now # - name: Upload coverage reports to Codecov diff --git a/deno.json b/deno.json index 9a83af5..68e21c0 100644 --- a/deno.json +++ b/deno.json @@ -3,7 +3,7 @@ "format": "deno fmt ./src/", "lint": "deno lint ./src/", "test": "deno test --coverage=.coverage", - "coverage": "deno coverage .coverage --lcov --excluse=/src/node/ --exclude=/test/ --exclude=/scripts/ > ./.coverage/coverage.lcov", + "coverage": "deno coverage .coverage --lcov --exclude=/src/node/ --exclude=/test/ --exclude=/scripts/ > ./.coverage/coverage.lcov", "test:watch": "deno test --watch", "localTest": "deno run --allow-read --allow-write --allow-run ./scripts/watch-test.ts", "clean": "rm -r ./coverage", @@ -32,6 +32,7 @@ ] }, "exclude": [ + "./_local_testing.ts", "npm/" ] }, diff --git a/deno.lock b/deno.lock index e7b5957..ab35c82 100644 --- a/deno.lock +++ b/deno.lock @@ -1,46 +1,6 @@ { "version": "3", - "redirects": { - "https://crux.land/router@0.0.5": "https://crux.land/api/get/2KNRVU.ts" - }, "remote": { - "https://crux.land/api/get/2KNRVU.ts": "6a77d55844aba78d01520c5ff0b2f0af7f24cc1716a0de8b3bb6bd918c47b5ba", - "https://deno.land/std@0.140.0/_util/assert.ts": "e94f2eb37cebd7f199952e242c77654e43333c1ac4c5c700e929ea3aa5489f74", - "https://deno.land/std@0.140.0/_util/os.ts": "3b4c6e27febd119d36a416d7a97bd3b0251b77c88942c8f16ee5953ea13e2e49", - "https://deno.land/std@0.140.0/bytes/bytes_list.ts": "67eb118e0b7891d2f389dad4add35856f4ad5faab46318ff99653456c23b025d", - "https://deno.land/std@0.140.0/bytes/equals.ts": "fc16dff2090cced02497f16483de123dfa91e591029f985029193dfaa9d894c9", - "https://deno.land/std@0.140.0/bytes/mod.ts": "763f97d33051cc3f28af1a688dfe2830841192a9fea0cbaa55f927b49d49d0bf", - "https://deno.land/std@0.140.0/fmt/colors.ts": "30455035d6d728394781c10755351742dd731e3db6771b1843f9b9e490104d37", - "https://deno.land/std@0.140.0/fs/_util.ts": "0fb24eb4bfebc2c194fb1afdb42b9c3dda12e368f43e8f2321f84fc77d42cb0f", - "https://deno.land/std@0.140.0/fs/ensure_dir.ts": "9dc109c27df4098b9fc12d949612ae5c9c7169507660dcf9ad90631833209d9d", - "https://deno.land/std@0.140.0/io/buffer.ts": "bd0c4bf53db4b4be916ca5963e454bddfd3fcd45039041ea161dbf826817822b", - "https://deno.land/std@0.140.0/path/_constants.ts": "df1db3ffa6dd6d1252cc9617e5d72165cd2483df90e93833e13580687b6083c3", - "https://deno.land/std@0.140.0/path/_interface.ts": "ee3b431a336b80cf445441109d089b70d87d5e248f4f90ff906820889ecf8d09", - "https://deno.land/std@0.140.0/path/_util.ts": "c1e9686d0164e29f7d880b2158971d805b6e0efc3110d0b3e24e4b8af2190d2b", - "https://deno.land/std@0.140.0/path/common.ts": "bee563630abd2d97f99d83c96c2fa0cca7cee103e8cb4e7699ec4d5db7bd2633", - "https://deno.land/std@0.140.0/path/glob.ts": "cb5255638de1048973c3e69e420c77dc04f75755524cb3b2e160fe9277d939ee", - "https://deno.land/std@0.140.0/path/mod.ts": "d3e68d0abb393fb0bf94a6d07c46ec31dc755b544b13144dee931d8d5f06a52d", - "https://deno.land/std@0.140.0/path/posix.ts": "293cdaec3ecccec0a9cc2b534302dfe308adb6f10861fa183275d6695faace44", - "https://deno.land/std@0.140.0/path/separator.ts": "fe1816cb765a8068afb3e8f13ad272351c85cbc739af56dacfc7d93d710fe0f9", - "https://deno.land/std@0.140.0/path/win32.ts": "31811536855e19ba37a999cd8d1b62078235548d67902ece4aa6b814596dd757", - "https://deno.land/std@0.140.0/streams/conversion.ts": "712585bfa0172a97fb68dd46e784ae8ad59d11b88079d6a4ab098ff42e697d21", - "https://deno.land/std@0.181.0/_util/asserts.ts": "178dfc49a464aee693a7e285567b3d0b555dc805ff490505a8aae34f9cfb1462", - "https://deno.land/std@0.181.0/_util/os.ts": "d932f56d41e4f6a6093d56044e29ce637f8dcc43c5a90af43504a889cf1775e3", - "https://deno.land/std@0.181.0/fmt/colors.ts": "d67e3cd9f472535241a8e410d33423980bec45047e343577554d3356e1f0ef4e", - "https://deno.land/std@0.181.0/fs/_util.ts": "65381f341af1ff7f40198cee15c20f59951ac26e51ddc651c5293e24f9ce6f32", - "https://deno.land/std@0.181.0/fs/empty_dir.ts": "c3d2da4c7352fab1cf144a1ecfef58090769e8af633678e0f3fabaef98594688", - "https://deno.land/std@0.181.0/fs/ensure_dir.ts": "dc64c4c75c64721d4e3fb681f1382f803ff3d2868f08563ff923fdd20d071c40", - "https://deno.land/std@0.181.0/fs/expand_glob.ts": "e4f56259a0a70fe23f05215b00de3ac5e6ba46646ab2a06ebbe9b010f81c972a", - "https://deno.land/std@0.181.0/fs/walk.ts": "ea95ffa6500c1eda6b365be488c056edc7c883a1db41ef46ec3bf057b1c0fe32", - "https://deno.land/std@0.181.0/path/_constants.ts": "e49961f6f4f48039c0dfed3c3f93e963ca3d92791c9d478ac5b43183413136e0", - "https://deno.land/std@0.181.0/path/_interface.ts": "6471159dfbbc357e03882c2266d21ef9afdb1e4aa771b0545e90db58a0ba314b", - "https://deno.land/std@0.181.0/path/_util.ts": "d7abb1e0dea065f427b89156e28cdeb32b045870acdf865833ba808a73b576d0", - "https://deno.land/std@0.181.0/path/common.ts": "ee7505ab01fd22de3963b64e46cff31f40de34f9f8de1fff6a1bd2fe79380000", - "https://deno.land/std@0.181.0/path/glob.ts": "d479e0a695621c94d3fd7fe7abd4f9499caf32a8de13f25073451c6ef420a4e1", - "https://deno.land/std@0.181.0/path/mod.ts": "bf718f19a4fdd545aee1b06409ca0805bd1b68ecf876605ce632e932fe54510c", - "https://deno.land/std@0.181.0/path/posix.ts": "8b7c67ac338714b30c816079303d0285dd24af6b284f7ad63da5b27372a2c94d", - "https://deno.land/std@0.181.0/path/separator.ts": "0fb679739d0d1d7bf45b68dacfb4ec7563597a902edbaf3c59b50d5bcadd93b1", - "https://deno.land/std@0.181.0/path/win32.ts": "d186344e5583bcbf8b18af416d13d82b35a317116e6460a5a3953508c3de5bba", "https://deno.land/std@0.207.0/assert/_constants.ts": "8a9da298c26750b28b326b297316cdde860bc237533b07e1337c021379e6b2a9", "https://deno.land/std@0.207.0/assert/_diff.ts": "58e1461cc61d8eb1eacbf2a010932bf6a05b79344b02ca38095f9b805795dc48", "https://deno.land/std@0.207.0/assert/_format.ts": "a69126e8a469009adf4cf2a50af889aca364c349797e63174884a52ff75cf4c7", @@ -164,48 +124,14 @@ "https://deno.land/std@0.207.0/path/windows/separator.ts": "ae21f27015f10510ed1ac4a0ba9c4c9c967cbdd9d9e776a3e4967553c397bd5d", "https://deno.land/std@0.207.0/path/windows/to_file_url.ts": "8e9ea9e1ff364aa06fa72999204229952d0a279dbb876b7b838b2b2fea55cce3", "https://deno.land/std@0.207.0/path/windows/to_namespaced_path.ts": "e0f4d4a5e77f28a5708c1a33ff24360f35637ba6d8f103d19661255ef7bfd50d", - "https://deno.land/std@0.207.0/testing/_test_suite.ts": "30f018feeb3835f12ab198d8a518f9089b1bcb2e8c838a8b615ab10d5005465c", - "https://deno.land/std@0.207.0/testing/asserts.ts": "605bbd2ef0695e2a4324d810c4ad22e56041d51afb9584fc0b4e81084b14b1d6", - "https://deno.land/std@0.207.0/testing/bdd.ts": "3f446df5ef8e856a869e8eec54c8482590415741ff0b6358a00c43486cc15769", - "https://deno.land/x/code_block_writer@12.0.0/mod.ts": "2c3448060e47c9d08604c8f40dee34343f553f33edcdfebbf648442be33205e5", - "https://deno.land/x/code_block_writer@12.0.0/utils/string_utils.ts": "60cb4ec8bd335bf241ef785ccec51e809d576ff8e8d29da43d2273b69ce2a6ff", - "https://deno.land/x/deno_cache@0.6.2/auth_tokens.ts": "5d1d56474c54a9d152e44d43ea17c2e6a398dd1e9682c69811a313567c01ee1e", - "https://deno.land/x/deno_cache@0.6.2/cache.ts": "58b53c128b742757efcad10af9a3871f23b4e200674cb5b0ddf61164fb9b2fe7", - "https://deno.land/x/deno_cache@0.6.2/deno_dir.ts": "1ea355b8ba11c630d076b222b197cfc937dd81e5a4a260938997da99e8ff93a0", - "https://deno.land/x/deno_cache@0.6.2/deps.ts": "12cca94516cf2d3ed42fccd4b721ecd8060679253f077d83057511045b0081aa", - "https://deno.land/x/deno_cache@0.6.2/dirs.ts": "009c6f54e0b610914d6ce9f72f6f6ccfffd2d47a79a19061e0a9eb4253836069", - "https://deno.land/x/deno_cache@0.6.2/disk_cache.ts": "66a1e604a8d564b6dd0500326cac33d08b561d331036bf7272def80f2f7952aa", - "https://deno.land/x/deno_cache@0.6.2/file_fetcher.ts": "4f3e4a2c78a5ca1e4812099e5083f815a8525ab20d389b560b3517f6b1161dd6", - "https://deno.land/x/deno_cache@0.6.2/http_cache.ts": "407135eaf2802809ed373c230d57da7ef8dff923c4abf205410b9b99886491fd", - "https://deno.land/x/deno_cache@0.6.2/lib/deno_cache_dir.generated.js": "59f8defac32e8ebf2a30f7bc77e9d88f0e60098463fb1b75e00b9791a4bbd733", - "https://deno.land/x/deno_cache@0.6.2/lib/snippets/deno_cache_dir-a2aecaa9536c9402/fs.js": "cbe3a976ed63c72c7cb34ef845c27013033a3b11f9d8d3e2c4aa5dda2c0c7af6", - "https://deno.land/x/deno_cache@0.6.2/mod.ts": "b4004287e1c6123d7f07fe9b5b3e94ce6d990c4102949a89c527c68b19627867", - "https://deno.land/x/deno_cache@0.6.2/util.ts": "f3f5a0cfc60051f09162942fb0ee87a0e27b11a12aec4c22076e3006be4cc1e2", - "https://deno.land/x/dir@1.5.1/data_local_dir/mod.ts": "91eb1c4bfadfbeda30171007bac6d85aadacd43224a5ed721bbe56bc64e9eb66", - "https://deno.land/x/dnt@0.39.0/lib/compiler.ts": "7f4447531581896348b8a379ab94730856b42ae50d99043f2468328360293cb1", - "https://deno.land/x/dnt@0.39.0/lib/compiler_transforms.ts": "f21aba052f5dcf0b0595c734450842855c7f572e96165d3d34f8fed2fc1f7ba1", - "https://deno.land/x/dnt@0.39.0/lib/mod.deps.ts": "8d6123c8e1162037e58aa8126686a03d1e2cffb250a8757bf715f80242097597", - "https://deno.land/x/dnt@0.39.0/lib/npm_ignore.ts": "57fbb7e7b935417d225eec586c6aa240288905eb095847d3f6a88e290209df4e", - "https://deno.land/x/dnt@0.39.0/lib/package_json.ts": "607b0a4f44acad071a4c8533b312a27d6671eac8e6a23625c8350ce29eadb2ba", - "https://deno.land/x/dnt@0.39.0/lib/pkg/dnt_wasm.generated.js": "4f9c59b3ca6c875adabb10df256e273fff1129fca3a1557eb8936bddd7da7b18", - "https://deno.land/x/dnt@0.39.0/lib/pkg/snippets/dnt-wasm-a15ef721fa5290c5/helpers.js": "aba69a019a6da6f084898a6c7b903b8b583bc0dbd82bfb338449cf0b5bce58fd", - "https://deno.land/x/dnt@0.39.0/lib/shims.ts": "60fd285ad433c6944544595e7b885eab3eab09253252891380654f4cd3addaaa", - "https://deno.land/x/dnt@0.39.0/lib/test_runner/get_test_runner_code.ts": "4dc7a73a13b027341c0688df2b29a4ef102f287c126f134c33f69f0339b46968", - "https://deno.land/x/dnt@0.39.0/lib/test_runner/test_runner.ts": "4d0da0500ec427d5f390d9a8d42fb882fbeccc92c92d66b6f2e758606dbd40e6", - "https://deno.land/x/dnt@0.39.0/lib/transform.deps.ts": "2e159661e1c5c650de9a573babe0e319349fe493105157307ec2ad2f6a52c94e", - "https://deno.land/x/dnt@0.39.0/lib/types.ts": "b8e228b2fac44c2ae902fbb73b1689f6ab889915bd66486c8a85c0c24255f5fb", - "https://deno.land/x/dnt@0.39.0/lib/utils.ts": "224f15f33e7226a2fd991e438d0291d7ed8c7889807efa2e1ecb67d2d1db6720", - "https://deno.land/x/dnt@0.39.0/mod.ts": "9df36a862161d9eb376472b699f6cb08ba0ad1704e0826fbe13be766bd3c01da", - "https://deno.land/x/dnt@0.39.0/transform.ts": "f68743a14cf9bf53bfc9c81073871d69d447a7f9e3453e0447ca2fb78926bb1d", - "https://deno.land/x/mock_fetch@0.3.0/mod.ts": "7e7806c65ab17b2b684c334c4e565812bdaf504a3e9c938d2bb52bb67428bc89", - "https://deno.land/x/ts_morph@20.0.0/bootstrap/mod.ts": "b53aad517f106c4079971fcd4a81ab79fadc40b50061a3ab2b741a09119d51e9", - "https://deno.land/x/ts_morph@20.0.0/bootstrap/ts_morph_bootstrap.js": "6645ac03c5e6687dfa8c78109dc5df0250b811ecb3aea2d97c504c35e8401c06", - "https://deno.land/x/ts_morph@20.0.0/common/DenoRuntime.ts": "6a7180f0c6e90dcf23ccffc86aa8271c20b1c4f34c570588d08a45880b7e172d", - "https://deno.land/x/ts_morph@20.0.0/common/mod.ts": "01985d2ee7da8d1caee318a9d07664774fbee4e31602bc2bb6bb62c3489555ed", - "https://deno.land/x/ts_morph@20.0.0/common/ts_morph_common.js": "2325f94f61dc5f3f98a1dab366dc93048d11b1433d718b10cfc6ee5a1cfebe8f", - "https://deno.land/x/ts_morph@20.0.0/common/typescript.js": "b9edf0a451685d13e0467a7ed4351d112b74bd1e256b915a2b941054e31c1736", - "https://deno.land/x/wasmbuild@0.15.1/cache.ts": "9d01b5cb24e7f2a942bbd8d14b093751fa690a6cde8e21709ddc97667e6669ed", - "https://deno.land/x/wasmbuild@0.15.1/loader.ts": "8c2fc10e21678e42f84c5135d8ab6ab7dc92424c3f05d2354896a29ccfd02a63", + "https://deno.land/std@0.207.0/testing/mock.ts": "5c4363d6846179390c1ef4347ea0fddfd9780705089228cba331dc49c2a4f3c1", + "https://deno.land/x/deno_mock_fetch@1.0.1/mock-fetch.error.ts": "c75e83b959d0b4b18bc5434c15dc5ab40a37fe6069cb51d7607a22391a7b22af", + "https://deno.land/x/deno_mock_fetch@1.0.1/mock-fetch.ts": "bc06fb96910aea4c042b7c7387887fecd70f272b7230de0262711c878066111c", + "https://deno.land/x/deno_mock_fetch@1.0.1/mock-fetch.type.ts": "c03dc0cb93b4e68496813e9c0a322e3e2a08d16fb78f9b7196a5328cbc0ed771", + "https://deno.land/x/deno_mock_fetch@1.0.1/mock-interceptor.ts": "659014b78275a679fc4644d1f36ba486a27eaa7149c2f3f32fa34a2e7a085059", + "https://deno.land/x/deno_mock_fetch@1.0.1/mock-scope.ts": "f7235b1efa7371b0698f0b5122eb82d042063411429585e4cdf740f2ae60af94", + "https://deno.land/x/deno_mock_fetch@1.0.1/mock-utils.ts": "3a7b12704a75eee2e9721221c3da80bd70ab4f00fdbd1b786fb240f1be20b497", + "https://deno.land/x/deno_mock_fetch@1.0.1/mod.ts": "7ea9e35228454b578ff47d42602eba53de0174ffa4ca5567308b11384b55ddd7", "https://deno.land/x/xml@2.1.3/mod.ts": "4a314a7a28d1ec92f899ce4c6991f0356c77550a75955ec3f4a36733f08548e8", "https://deno.land/x/xml@2.1.3/parse.ts": "614b8648345ae93c641368836947484d321c7ac9312ae12ec750434353cd7385", "https://deno.land/x/xml@2.1.3/stringify.ts": "930d35431f153b29d36549cff08fcfbe978e52ccb56af1e3baa2e0760f418b04", diff --git a/dev_deps.ts b/dev_deps.ts index c07e885..d511dcc 100644 --- a/dev_deps.ts +++ b/dev_deps.ts @@ -1,17 +1,4 @@ -export { - assertEquals, - assertMatch, - assertNotEquals, - assertThrows, -} from "https://deno.land/std@0.207.0/testing/asserts.ts"; +export { assertEquals, assertRejects } from "https://deno.land/std@0.207.0/assert/mod.ts"; export { emptyDir } from "https://deno.land/std@0.207.0/fs/mod.ts"; -import * as mf from "https://deno.land/x/mock_fetch@0.3.0/mod.ts"; -export { mf }; -export { - afterAll, - afterEach, - beforeAll, - beforeEach, - describe, - it, -} from "https://deno.land/std@0.207.0/testing/bdd.ts"; +export { MockFetch } from "https://deno.land/x/deno_mock_fetch@1.0.1/mod.ts"; +export { resolvesNext, stub } from "https://deno.land/std@0.207.0/testing/mock.ts"; diff --git a/mocks/fetch.ts b/mocks/fetch.ts deleted file mode 100644 index 93efbc0..0000000 --- a/mocks/fetch.ts +++ /dev/null @@ -1 +0,0 @@ -// WIP https://github.com/aegrumet/umbilical/blob/32331bdac3131e5cef140c5798204e48fd5b988e/mocks/fetch.ts diff --git a/src/deno/Pastebin.ts b/src/deno/Pastebin.ts index 0e8af19..41ab7ce 100644 --- a/src/deno/Pastebin.ts +++ b/src/deno/Pastebin.ts @@ -2,6 +2,12 @@ import { AbstractPastebin } from "../lib/Pastebin.ts"; import { ICreatePasteFileOptions, ICreatePasteTextOptions } from "../lib/interfaces.ts"; export class Pastebin extends AbstractPastebin { + /** + * Create a paste from a file + * + * @param options { ICreatePasteFileOptions } options for creating a paste + * @returns { Promise } returns the url of the created paste + */ async createPasteFromFile( options: ICreatePasteFileOptions = { file: "" }, ): Promise { diff --git a/src/lib/Pastebin.ts b/src/lib/Pastebin.ts index a9beef0..593d33b 100644 --- a/src/lib/Pastebin.ts +++ b/src/lib/Pastebin.ts @@ -23,7 +23,7 @@ const ENDPOINTS = { // @deno-fmt-ignore const formatTypeArr: FormatType[] = [ "4cs", "6502acme", "6502kickass", "6502tasm", "abap", "actionscript", "actionscript3", "ada", "aimms", "algol68", "apache", "applescript", "apt_sources", "arduino", "arm", "asm", "asp", "asymptote", "autoconf", "autohotkey", "autoit", "avisynth", "awk", "bascomavr", "bash", "basic4gl", "dos", "bibtex", "b3d", "blitzbasic", "bmx", "bnf", "boo", "bf", "c", "csharp", "c_winapi", "cpp", "cpp-winapi", "cpp-qt", "c_loadrunner", "caddcl", "cadlisp", "ceylon", "cfdg", "c_mac", "chaiscript", "chapel", "cil", "clojure", "klonec", "klonecpp", "cmake", "cobol", "coffeescript", "cfm", "css", "cuesheet", "d", "dart", "dcl", "dcpu16", "dcs", "delphi", "oxygene", "diff", "div", "dot", "e", "ezt", "ecmascript", "eiffel", "email", "epc", "erlang", "euphoria", "fsharp", "falcon", "filemaker", "fo", "f1", "fortran", "freebasic", "freeswitch", "gambas", "gml", "gdb", "gdscript", "genero", "genie", "gettext", "go", "godot-glsl", "groovy", "gwbasic", "haskell", "haxe", "hicest", "hq9plus", "html4strict", "html5", "icon", "idl", "ini", "inno", "intercal", "io", "ispfpanel", "j", "java", "java5", "javascript", "jcl", "jquery", "json", "julia", "kixtart", "kotlin", "ksp", "latex", "ldif", "lb", "lsl2", "lisp", "llvm", "locobasic", "logtalk", "lolcode", "lotusformulas", "lotusscript", "lscript", "lua", "m68k", "magiksf", "make", "mapbasic", "markdown", "matlab", "mercury", "metapost", "mirc", "mmix", "mk-61", "modula2", "modula3", "68000devpac", "mpasm", "mxml", "mysql", "nagios", "netrexx", "newlisp", "nginx", "nim", "nsis", "oberon2", "objeck", "objc", "ocaml", "ocaml-brief", "octave", "pf", "glsl", "oorexx", "oobas", "oracle8", "oracle11", "oz", "parasail", "parigp", "pascal", "pawn", "pcre", "per", "perl", "perl6", "phix", "php", "php-brief", "pic16", "pike", "pixelbender", "pli", "plsql", "postgresql", "postscript", "povray", "powerbuilder", "powershell", "proftpd", "progress", "prolog", "properties", "providex", "puppet", "purebasic", "pycon", "python", "pys60", "q", "qbasic", "qml", "rsplus", "racket", "rails", "rbs", "rebol", "reg", "rexx", "robots", "roff", "rpmspec", "ruby", "gnuplot", "rust", "sas", "scala", "scheme", "scilab", "scl", "sdlbasic", "smalltalk", "smarty", "spark", "sparql", "sqf", "sql", "sshconfig", "standardml", "stonescript", "sclang", "swift", "systemverilog", "tsql", "tcl", "teraterm", "texgraph", "thinbasic", "typescript", "typoscript", "unicon", "uscript", "upc", "urbi", "vala", "vbnet", "vbscript", "vedit", "verilog", "vhdl", "vim", "vb", "visualfoxpro", "visualprolog", "whitespace", "whois", "winbatch", "xbasic", "xml", "xojo", "xorg_conf", "xpp", "yaml", "yara", "z80", "zxbasic" ]; -const expirationTimeArr: string[] = Object.keys(ExpirationTime); +const expirationTimeArr: string[] = Object.values(ExpirationTime); export abstract class AbstractPastebin { #config: IPastebinOptions; @@ -31,6 +31,12 @@ export abstract class AbstractPastebin { // We're able to overwrite fetch because it is an abstract class fetch = globalThis.fetch; + // RequestTimeout = 4000; + requestTimeout = 4000; + + // DEBUG + #debug = false; + constructor(config?: IPastebinOptions | string | null) { if (isUndefined(config) || isNull(config)) { this.#config = {}; @@ -42,6 +48,9 @@ export abstract class AbstractPastebin { conf = { api_dev_key: conf }; } + this.#debug = conf.debug === true; + delete conf.debug; + this.#config = Object.assign({ api_dev_key: null, api_user_key: null, @@ -50,10 +59,16 @@ export abstract class AbstractPastebin { }, conf); } + setDebug(debug: boolean): void { + this.#debug = debug; + } + /** + * Get the content of a paste + * * @param id ID of the paste * @param isPrivate is the paste private? Needs authentication - * @returns + * @returns { Promise } returns the content of the paste */ async getPaste( id: string, @@ -63,6 +78,9 @@ export abstract class AbstractPastebin { if (ID.startsWith("https://pastebin.com/")) { ID = ID.replace("https://pastebin.com/", ""); } + if (!ID || ID.length === 0) { + return Promise.reject(new Error("Invalid paste url or id")); + } if (isPrivate) { const params = this.#createParams("show_paste"); params.api_paste_key = ID; @@ -79,6 +97,12 @@ export abstract class AbstractPastebin { return this.#getRequest(ENDPOINTS.RAW + ID); } + /** + * Create a paste + * + * @param options { ICreatePasteTextOptions } options for creating a paste + * @returns { Promise } returns the url of the created paste + */ async createPaste(options: ICreatePasteTextOptions): Promise { if (!this.hasDevKey) { return Promise.reject(new Error("Dev key needed!")); @@ -118,7 +142,7 @@ export abstract class AbstractPastebin { if (typeof format === "string") { if (formatTypeArr.includes(format as FormatType)) { - params.api_paste_format = format; + params.api_paste_format = format as FormatType; } else { return Promise.reject( new Error(`Paste format ${options.format} is unknown!`), @@ -140,7 +164,7 @@ export abstract class AbstractPastebin { if (typeof expiration === "string") { if (expirationTimeArr.includes(expiration)) { - params.api_paste_expire_date = expiration; + params.api_paste_expire_date = expiration as ExpirationTime; } else { return Promise.reject( new Error(`Expiration format '${expiration}' is unknown!`), @@ -155,6 +179,12 @@ export abstract class AbstractPastebin { return this.#postRequest(ENDPOINTS.POST, params); } + /** + * Delete a paste + * + * @param pasteID { string } ID of the paste + * @returns { Promise } returns the response of the request + */ async deletePaste(pasteID: string): Promise { if (!this.hasDevKey) { return Promise.reject(new Error("Dev key needed!")); @@ -178,6 +208,14 @@ export abstract class AbstractPastebin { return this.#postRequest(ENDPOINTS.POST, params); } + /** + * List all pastes of a user + * + * @param limit { number } limit of pastes to return (default: 50, max: 1000) + * @returns { Promise } returns an array of pastes + * @throws { Error } throws an error if the limit is not between 1 and 1000 + * @throws { Error } throws an error if the dev key is missing + */ async listUserPastes(limit = 50): Promise { if (limit < 1 || limit > 1000) { return Promise.reject( @@ -203,6 +241,12 @@ export abstract class AbstractPastebin { return this.#postAndParse(params, this.#parsePastes); } + /** + * Get user information + * + * @returns { Promise } returns the user information + * @throws { Error } throws an error if the dev key is missing + */ async getUserInfo(): Promise { if (!this.hasDevKey) { return Promise.reject(new Error("Dev key needed!")); @@ -315,14 +359,17 @@ export abstract class AbstractPastebin { method: "GET" | "POST", params: Record | IPasteAPIOptions = {}, ): RequestInit { - const abortController = AbortController ? new AbortController() : undefined; - const timeout = 4000; + const abortController = this.requestTimeout > 0 && AbortController + ? new AbortController() + : undefined; - const _timeoutId = setTimeout(() => { - if (abortController) { - abortController.abort(); - } - }, timeout); + if (abortController) { + const _timeoutId = setTimeout(() => { + if (abortController) { + abortController.abort(); + } + }, this.requestTimeout); + } const init: RequestInit = { headers: new Headers({ @@ -351,10 +398,6 @@ export abstract class AbstractPastebin { resolve: (value: string | PromiseLike) => void, reject: (reason?: unknown) => void, ): Promise { - if (!res || res === null) { - reject(new Error("No response!")); - return; - } if (res.status === 404) { reject(new Error("Not found!")); return; @@ -394,11 +437,6 @@ export abstract class AbstractPastebin { return new Promise((resolve, reject) => { const options = this.#getRequestOptions(method, params); - if (!path) { - reject(new Error("No path provided!")); - return; - } - this.fetch(path, options).then((res) => { this.#handleResponse(res, resolve, reject); }).catch((err) => { @@ -408,13 +446,27 @@ export abstract class AbstractPastebin { } #getRequest(path: string): Promise { + this.#debugger(">>>>> getRequest", path); return this.#abstractRequest("GET", path); } #postRequest(path: string, params: Record | IPasteAPIOptions): Promise { + this.#debugger(">>>>> postRequest", path, params); return this.#abstractRequest("POST", path, params); } + #debugger(...args: unknown[]): void { + if (this.#debug) { + console.log(...args); + } + } + + // Getters + + get debug(): boolean { + return this.#debug; + } + get config(): IPastebinOptions { return this.#config; } diff --git a/src/lib/interfaces.ts b/src/lib/interfaces.ts index e1d3db8..d72a7e7 100644 --- a/src/lib/interfaces.ts +++ b/src/lib/interfaces.ts @@ -3,6 +3,7 @@ export interface IPastebinOptions { api_user_key?: null | string; api_user_name?: null | string; api_user_password?: null | string; + debug?: boolean; } export enum PrivacyLevel { @@ -297,9 +298,9 @@ export interface ICreatePasteBaseOptions { text?: string; file?: string | T; title?: string; - format?: FormatType; - privacy?: PrivacyLevel; - expiration?: ExpirationTime | null; + format?: FormatType | string; + privacy?: PrivacyLevel | number; + expiration?: ExpirationTime | string | null; } export interface ICreatePasteTextOptions extends ICreatePasteBaseOptions { diff --git a/test/Pastebin.test.ts b/test/Pastebin.test.ts new file mode 100644 index 0000000..0a3eba6 --- /dev/null +++ b/test/Pastebin.test.ts @@ -0,0 +1,413 @@ +import { assertEquals, assertRejects, MockFetch, resolvesNext, stub } from "../dev_deps.ts"; +import { ExpirationTime, IPastebinOptions, Pastebin, PrivacyLevel } from "../mod.ts"; + +// We're mocking Pastebin with a timeout of 0, so we don't introduce unwanted timeouts +class TestPastebin extends Pastebin { + requestTimeout = 0; +} + +const defaultOptions: IPastebinOptions = { + api_dev_key: "test", + api_user_name: "User", + api_user_password: "Password", +}; + +Deno.test("Pastebin", async (t) => { + const mf = new MockFetch(); + + const setKeyReturn = () => + mf.intercept("https://pastebin.com/api/api_login.php", { + method: "POST", + }).response("12345678901234567890123456789012"); + + await t.step("getPaste - anonymous (with debug)", async () => { + const pastebin = new TestPastebin({ + debug: true, + }); + + mf.intercept("https://pastebin.com/raw.php?i=7EAT0yPS").response("Hello World!").times(2); + + const paste1 = await pastebin.getPaste("https://pastebin.com/7EAT0yPS"); + const paste2 = await pastebin.getPaste("7EAT0yPS"); + + assertEquals(pastebin.debug, true, "debug should be set"); + pastebin.setDebug(false); + assertEquals(pastebin.debug, false, "debug should be set"); + + assertEquals(paste1, "Hello World!", "paste with url should return Hello World!"); + assertEquals(paste2, "Hello World!", "paste with id should return Hello World!"); + }); + + await t.step("getPaste - user", async () => { + const pastebin = new TestPastebin(defaultOptions); + + mf.intercept("https://pastebin.com/api/api_raw.php", { + method: "POST", + }).response("Hello World!").times(2); + + // Mimick a too small key + mf.intercept("https://pastebin.com/api/api_login.php", { + method: "POST", + }).response("too_small_key"); + + assertRejects( + () => pastebin.getPaste("https://pastebin.com/test", true), + ); + + setKeyReturn(); + + const paste1 = await pastebin.getPaste("https://pastebin.com/testid", true); + assertEquals( + pastebin.config.api_user_key, + "12345678901234567890123456789012", + "api_user_key should be set", + ); + assertEquals(paste1, "Hello World!", "paste with url should return Hello World!"); + + const paste2 = await pastebin.getPaste("testid", true); + assertEquals(paste2, "Hello World!", "paste with id should return Hello World!"); + }); + + await t.step("getPaste - errors", () => { + const pastebin = new Pastebin(""); + + assertRejects( + () => pastebin.getPaste(""), + Error, + "Invalid paste url or id", + ); + + assertRejects( + () => pastebin.getPaste("https://pastebin.com/test", true), + Error, + ); + }); + + await t.step("createPaste - anonymous", async () => { + const pastebinWithoutKey = new TestPastebin(); + const pastebinWithKey = new TestPastebin("DEV_KEY"); + + mf.intercept("https://pastebin.com/api/api_post.php", { + method: "POST", + }).response("https://pastebin.com/test"); + + assertRejects( + // @ts-ignore - Testing invalid options + () => pastebinWithoutKey.createPaste(), + ); + + assertRejects( + // @ts-ignore - Testing invalid options + () => pastebinWithKey.createPaste(), + ); + + assertRejects( + () => + pastebinWithKey.createPaste({ + text: "", + }), + ); + + assertRejects( + () => + pastebinWithKey.createPaste({ + // @ts-ignore - Testing invalid options + text: 1, + }), + ); + + assertRejects( + () => + pastebinWithKey.createPaste({ + text: "valid", + expiration: "invalid", + }), + ); + + assertRejects( + () => + pastebinWithKey.createPaste({ + text: "valid", + expiration: "invalid", + }), + ); + + assertRejects( + () => + pastebinWithKey.createPaste({ + text: "valid", + format: "invalid", + }), + ); + + assertRejects( + () => + pastebinWithKey.createPaste({ + text: "valid", + privacy: PrivacyLevel.PRIVATE, + }), + ); + + assertRejects( + () => + pastebinWithKey.createPaste({ + text: "valid", + privacy: -1, + }), + ); + + const paste = await pastebinWithKey.createPaste({ + text: "Hello World!", + title: "Hello World", + format: "apache", + expiration: ExpirationTime.ONE_DAY, + }); + + assertEquals(paste, "https://pastebin.com/test", "paste should return url"); + }); + + await t.step("createPaste - user", async () => { + const pastebin = new TestPastebin(defaultOptions); + + setKeyReturn(); + mf.intercept("https://pastebin.com/api/api_post.php", { + method: "POST", + }).response("https://pastebin.com/test"); + + const paste = await pastebin.createPaste({ + text: "Hello World!", + title: "Hello World", + format: "apache", + expiration: ExpirationTime.ONE_DAY, + }); + + assertEquals(paste, "https://pastebin.com/test", "paste should return url"); + }); + + await t.step("createPasteFromFile", async () => { + const stubReadFile = stub( + Deno, + "readTextFile", + resolvesNext([ + "Hellow world", + "", + Promise.reject(new Error("Error reading file!")), + ]), + ); + const pastebin = new TestPastebin(defaultOptions); + const pastebinWithoutKey = new TestPastebin(); + + assertRejects( + () => pastebinWithoutKey.createPasteFromFile(), + ); + + setKeyReturn(); + mf.intercept("https://pastebin.com/api/api_post.php", { + method: "POST", + }).response("https://pastebin.com/test").times(2); + + const paste1 = await pastebin.createPasteFromFile({ + file: new Uint8Array(8).fill(0), + title: "Hello World", + format: "apache", + expiration: ExpirationTime.ONE_DAY, + }); + + const paste2 = await pastebin.createPasteFromFile({ + file: "./test.txt", + title: "Hello World", + format: "apache", + expiration: ExpirationTime.ONE_DAY, + }); + + assertRejects( + () => + pastebin.createPasteFromFile({ + file: new Uint8Array(0), + title: "Hello World", + format: "apache", + expiration: ExpirationTime.ONE_DAY, + }), + ); + + assertRejects( + () => + pastebin.createPasteFromFile({ + file: "./empty.txt", + title: "Hello World", + format: "apache", + expiration: ExpirationTime.ONE_DAY, + }), + ); + + assertRejects( + () => + pastebin.createPasteFromFile({ + file: "./empty.txt", + title: "Hello World", + format: "apache", + expiration: ExpirationTime.ONE_DAY, + }), + ); + + assertEquals(paste1, "https://pastebin.com/test", "paste should return url"); + assertEquals(paste2, "https://pastebin.com/test", "paste should return url"); + + stubReadFile.restore(); + }); + + await t.step("deletePaste", async () => { + const pastebin = new TestPastebin(defaultOptions); + const pastebinWithoutKey = new TestPastebin(); + const pastebinWithoutKey2 = new TestPastebin({ api_dev_key: "test" }); + + assertRejects( + () => pastebinWithoutKey.deletePaste("test"), + ); + + assertRejects( + () => pastebinWithoutKey2.deletePaste("test"), + ); + + setKeyReturn(); + mf.intercept("https://pastebin.com/api/api_post.php", { + method: "POST", + }).response("Paste Removed"); + + const paste = await pastebin.deletePaste("https://pastebin.com/test"); + + assertEquals(paste, "Paste Removed", "paste should return Paste Removed"); + }); + + await t.step("listUserPastes", async () => { + const pastebin = new TestPastebin(defaultOptions); + const pastebinWithoutKey = new TestPastebin(); + const pastebinWithoutKey2 = new TestPastebin({ api_dev_key: "test" }); + + assertRejects( + () => pastebinWithoutKey.listUserPastes(), + ); + + assertRejects( + () => pastebinWithoutKey2.listUserPastes(), + ); + + assertRejects( + () => pastebin.listUserPastes(1200), + ); + + setKeyReturn(); + + mf.intercept("https://pastebin.com/api/api_post.php", { + method: "POST", + }).response(); + + assertRejects( + () => pastebin.listUserPastes(), + ); + + mf.intercept("https://pastebin.com/api/api_post.php", { + method: "POST", + }).response( + ` + test + 1234567890 + test + 1234 + 1234567890 + 1 + apache + apache + https://pastebin.com/test + 123 + `.repeat(10), + ); + + const pastes = await pastebin.listUserPastes(); + + assertEquals(pastes.length, 10, "pastes should return 10 items"); + }); + + await t.step("getUserInfo", async () => { + const pastebin = new TestPastebin(defaultOptions); + const pastebinWithoutKey = new TestPastebin(); + const pastebinWithoutKey2 = new TestPastebin({ api_dev_key: "test" }); + + assertRejects( + () => pastebinWithoutKey.getUserInfo(), + ); + + assertRejects( + () => pastebinWithoutKey2.getUserInfo(), + ); + + setKeyReturn(); + + mf.intercept("https://pastebin.com/api/api_post.php", { + method: "POST", + }).response(); + + assertRejects( + () => pastebin.getUserInfo(), + ); + + mf.intercept("https://pastebin.com/api/api_post.php", { + method: "POST", + }).response( + ` + test + apache + 1M + https://pastebin.com/avatar/test + 1 + https://pastebin.com/test + + + test + 0 + `, + ); + + const user = await pastebin.getUserInfo(); + + assertEquals(user.user_name, "test", "user should return test"); + }); + + await t.step("handleUserResponse", () => { + const pastebin = new TestPastebin(defaultOptions); + const id = "TEST"; + const rawURL = "https://pastebin.com/raw.php?i=TEST"; + + mf.intercept(rawURL).response(undefined, { status: 404 }); + + assertRejects( + () => pastebin.getPaste(id), + ); + + mf.intercept(rawURL).response(null, { status: 403 }); + + assertRejects( + () => pastebin.getPaste(id), + ); + + mf.intercept(rawURL).response(null, { status: 500 }); + + assertRejects( + () => pastebin.getPaste(id), + ); + + mf.intercept(rawURL).response("Bad API request, invalid api_option"); + + assertRejects( + () => pastebin.getPaste(id), + ); + + mf.intercept(rawURL).response("Post limit"); + + assertRejects( + () => pastebin.getPaste(id), + ); + }); + + mf.deactivate(); +});