merge latest node-fetch-cookies version

This commit is contained in:
jkhsjdhjs 2019-11-29 01:51:54 +01:00
commit 6bde778519
Signed by: jkhsjdhjs
GPG Key ID: BAC6ADBAB7D576CC
10 changed files with 540 additions and 59 deletions

7
.travis.yml Normal file
View File

@ -0,0 +1,7 @@
language: node_js
node_js:
- '13'
install:
- npm install
script:
- npm test

125
README.md
View File

@ -1,29 +1,59 @@
# flumm-fetch-cookies
A [flumm-fetch](https://gitfap.de/Flummi/kbotv3-modules/blob/master/src/inc/fetch.mjs) wrapper with support for cookies.
# flumm-fetch-cookies [![Build Status](https://travis-ci.org/kein-Bot/flumm-fetch-cookies.svg?branch=master)](https://travis-ci.org/kein-Bot/flumm-fetch-cookies)
A [flumm-fetch](https://github.com/kein-Bot/flumm-fetch) wrapper with support for cookies.
It supports reading/writing from/to a JSON cookie jar and keeps cookies in memory until you call `CookieJar.save()` to reduce disk I/O.
## Usage Example
### For upgrading from 1.2.x or below to 1.3.x or above, please read the [breaking API changes](#130-breaking-api-changes).
## Usage Examples
### with file...
```javascript
import {fetch, CookieJar} from "flumm-fetch-cookies";
(async () => {
// creates a CookieJar instance
let cookieJar = new CookieJar("rw", "jar.json");
const cookieJar = new CookieJar("jar.json");
// load cookies from the cookie jar
await cookieJar.load();
// usual fetch usage, except with one or multiple cookie jars as first parameter
const response = await fetch(cookieJar, "https://google.de");
const response = await fetch(cookieJar, "https://example.com");
// save the received cookies to disk
cookieJar.save();
await cookieJar.save();
})();
```
### ...or without
```javascript
import {fetch, CookieJar} from "node-fetch-cookies";
(async () => {
const cookieJar = new CookieJar();
// log in to some api
let response = await fetch(cookieJar, "https://example.com/api/login", {
method: "POST",
body: "credentials"
});
// do some requests you require login for
response = await fetch(cookieJar, "https://example.com/api/admin/drop-all-databases");
// and optionally log out again
response = await fetch(cookieJar, "https://example.com/api/logout");
})();
```
## Documentation
### fetch(cookieJar, url, options)
### async fetch(cookieJar, url[, options])
- `cookieJar` A [CookieJar](#class-cookiejar) instance, an array of CookieJar instances or null, if you don't want to send or store cookies.
- `url` and `options` as in https://github.com/bitinn/node-fetch#fetchurl-options
Returns a Promise resolving to a [Response](https://github.com/bitinn/node-fetch#class-response) instance on success.
### Class: CookieJar
A class that stores cookies.
@ -31,27 +61,53 @@ A class that stores cookies.
#### Properties
- `flags` The read/write flags as specified below.
- `file` The path of the cookie jar on the disk.
- `cookies` A [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) mapping cookie names to their properties.
- `cookies` A [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) mapping hostnames to maps, which map cookie names to the respective [Cookie](#class-cookie) instance.
#### new CookieJar(flags[, file, cookies])
- `flags` A string specifying whether cookies should be read and/or written from/to the jar when passing it as parameter to [fetch](#fetchcookiejar-url-options).
#### new CookieJar([file, flags = `rw`, cookies])
- `file` An optional string containing a relative or absolute path to the file on the disk to use.
- `flags` An optional string specifying whether cookies should be read and/or written from/to the jar when passing it as parameter to [fetch](#fetchcookiejar-url-options). Default: `rw`
- `r`: only read from this jar
- `w`: only write to this jar
- `rw` or `wr`: read/write from/to this jar
- `file` An optional string containing a relative or absolute path to the file on the disk to use.
- `cookies` An optional initializer for the cookie jar - either an array of [Cookie](#class-cookie) instances or a single Cookie instance.
#### addCookie(cookie[, url])
#### addCookie(cookie[, fromURL])
Adds a cookie to the jar.
- `cookie` A [Cookie](#class-cookie) instance to add to the cookie jar. Alternatively this can also be a string, for example the string received from a website. In this case `url` should be specified.
- `url` The url a cookie has been received from.
- `cookie` A [Cookie](#class-cookie) instance to add to the cookie jar.
Alternatively this can also be a string, for example a serialized cookie received from a website.
In this case `fromURL` must be specified.
- `fromURL` The url a cookie has been received from.
#### forEach(callback)
Just a wrapper for `CookieJar.cookies.forEach(callback)`.
Returns `true` if the cookie has been added successfully. Returns `false` otherwise.
If the parser throws a [CookieParseError](#class-cookieparseerror) it will be caught and a warning will be printed to console.
#### save()
Saves the cookie jar to disk. Only non-expired cookies are saved.
#### domains()
Returns an iterator over all domains currently stored cookies for.
#### *cookiesDomain(domain)
Returns an iterator over all cookies currently stored for `domain`.
#### *cookiesValid(withSession)
Returns an iterator over all valid (non-expired) cookies.
- `withSession`: A boolean. Iterator will include session cookies if set to `true`.
#### *cookiesAll()
Returns an iterator over all cookies currently stored.
#### *cookiesValidForRequest(requestURL)
Returns an iterator over all cookies valid for a request to `url`.
#### deleteExpired(sessionEnded)
Removes all expired cookies from the jar.
- `sessionEnded`: A boolean. Also removes session cookies if set to `true`.
#### async load([file = this.file])
Reads cookies from `file` on the disk and adds the contained cookies.
- `file`: Path to the file where the cookies should be saved. Default: `this.file`, the file that has been passed to the constructor.
#### async save([file = this.file])
Saves the cookie jar to `file` on the disk. Only non-expired non-session cookies are saved.
- `file`: Path to the file where the cookies should be saved. Default: `this.file`, the file that has been passed to the constructor.
### Class: Cookie
An abstract representation of a cookie.
@ -59,27 +115,46 @@ An abstract representation of a cookie.
#### Properties
- `name` The identifier of the cookie.
- `value` The value of the cookie.
- `expiry` A [Date](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) object of the cookies expiry date.
- `expiry` A [Date](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) object of the cookies expiry date or `null`, if the cookie expires with the session.
- `domain` The domain the cookie is valid for.
- `path` The path the cookie is valid for.
- `secure` A boolean value representing the cookie's secure attribute. If set the cookie will only be used for `https` requests.
- `subdomains` A boolean value specifying whether the cookie should be used for subdomains of the domain or not.
- `subdomains` A boolean value specifying whether the cookie should be used for requests to subdomains of `domain` or not.
#### new Cookie(cookie, url)
- `cookie` The string representation of a cookie as send by a webserver.
#### new Cookie(str, requestURL)
Creates a cookie instance from the string representation of a cookie as send by a webserver.
- `str` The string representation of a cookie.
- `url` The url the cookie has been received from.
Will throw a `CookieParseError` if `str` couldn't be parsed.
#### static fromObject(obj)
Creates a cookie instance from an already existing object with the same properties.
#### serialize()
Serializes the cookie, transforming it to `name=value` so it can be used in requests.
#### hasExpired()
#### hasExpired(sessionEnded)
Returns whether the cookie has expired or not.
- `sessionEnded`: A boolean that specifies whether the current session has ended, meaning if set to `true`, the function will return `true` for session cookies.
#### isValidForRequest(requestURL)
Returns whether the cookie is valid for a request to `url`.
### Class: CookieParseError
The Error that is thrown when the cookie parser located in the constructor of the [Cookie](#class-cookie) class is unable to parse the input.
## 1.3.0 Breaking API Changes
- `new CookieJar(flags, file, cookies)` has been changed to `new CookieJar(file, flags = "rw", cookies)`.
`new CookieJar("rw")` can now be written as `new CookieJar()`, `new CookieJar("rw", "jar.json")` can now be written as `new CookieJar("jar.json")`.
This change has been introduced to simplify the usage of this library, since `rw` is used for `flags` in most cases anyways.
- `CookieJar.addFromFile(file)` has been renamed to the async function `async CookieJar.load([file = this.file])`, which uses the fsPromises API for non-blocking cookie loading.
The default value for `file` is the file passed to the constructor.
- `CookieJar.save(file)` was moved to `async CookieJar.save([file = this.file])` now also uses the fsPromises API.
- `new CookieJar()` now doesn't load cookies from the specified file anymore. To do so, call `await CookieJar.load()` after creating the CookieJar.
**NOTE: `CookieJar.load()` will throw an error if the cookie jar doesn't exist or doesn't contain valid JSON!**
#### isValidForRequest(url)
Returns whether the cookie is valid for a request to `url`. If not, it won't be send by the fetch wrapper.
## License
This project is licensed under the MIT license, see [LICENSE](LICENSE).

View File

@ -1,13 +1,16 @@
{
"name": "flumm-fetch-cookies",
"version": "1.1.2",
"version": "1.3.3",
"description": "flumm-fetch wrapper that adds support for cookie-jars",
"main": "src/index.mjs",
"engines": {
"node": ">=10.0.0"
"node": ">=11.14.0"
},
"files": [
"src/"
],
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"test": "node test/index.mjs"
},
"repository": {
"type": "git",

View File

@ -1,30 +1,46 @@
import Cookie from "./cookie.mjs";
import {promises as fs} from "fs";
import url from "url";
import Cookie from "./cookie.mjs";
import {paramError, CookieParseError} from "./errors.mjs";
export default class CookieJar {
constructor() {
constructor(file, flags = "rw", cookies) {
this.flags = flags;
this.file = file;
this.cookies = new Map();
if(typeof this.flags !== "string")
throw paramError("First", "flags", "new CookieJar()", "string");
if(this.file && typeof this.file !== "string")
throw paramError("Second", "file", "new CookieJar()", "string");
if(Array.isArray(cookies)) {
if(!cookies.every(c => c instanceof Cookie))
throw paramError("Third", "cookies", "new CookieJar()", "[Cookie]");
cookies.forEach(cookie => this.addCookie(cookie));
}
else if(cookies instanceof Cookie)
this.addCookie(cookies);
else if(cookies)
throw paramError("Third", "cookies", "new CookieJar()", ["[Cookie]", "Cookie"]);
}
addCookie(c, fromURL) {
if(typeof c === "string") {
addCookie(cookie, fromURL) {
if(typeof cookie === "string") {
try {
c = new Cookie(c, fromURL);
cookie = new Cookie(cookie, fromURL);
}
catch(error) {
if(error.name === "CookieParseError") {
console.warn("Ignored cookie: " + c);
if(error instanceof CookieParseError) {
console.warn("Ignored cookie: " + cookie);
console.warn("Reason: " + error.message);
return false;
}
else
throw error;
throw error;
}
}
else if(!(c instanceof Cookie))
throw new TypeError("First parameter is neither a string nor a cookie!");
if(!this.cookies.get(c.domain))
this.cookies.set(c.domain, new Map());
this.cookies.get(c.domain).set(c.name, c);
else if(!(cookie instanceof Cookie))
throw paramError("First", "cookie", "CookieJar.addCookie()", ["string", "Cookie"]);
if(!this.cookies.get(cookie.domain))
this.cookies.set(cookie.domain, new Map());
this.cookies.get(cookie.domain).set(cookie.name, cookie);
return true;
}
domains() {
@ -44,7 +60,7 @@ export default class CookieJar {
yield* this.cookiesDomain(domain);
}
*cookiesValidForRequest(requestURL) {
const namesYielded = [],
const namesYielded = new Set(),
domains = url
.parse(requestURL)
.hostname
@ -54,8 +70,8 @@ export default class CookieJar {
for(const domain of domains) {
for(const cookie of this.cookiesDomain(domain)) {
if(cookie.isValidForRequest(requestURL)
&& namesYielded.every(name => name !== cookie.name)) {
namesYielded.push(cookie.name);
&& !namesYielded.has(cookie.name)) {
namesYielded.add(cookie.name);
yield cookie;
}
}
@ -66,4 +82,15 @@ export default class CookieJar {
this.cookies = new Map();
validCookies.forEach(c => this.addCookie(c));
}
async load(file = this.file) {
if(typeof file !== "string")
throw new Error("No file has been specified for this cookie jar!");
JSON.parse(await fs.readFile(file)).forEach(c => this.addCookie(Cookie.fromObject(c)));
}
async save(file = this.file) {
if(typeof file !== "string")
throw new Error("No file has been specified for this cookie jar!");
// only save cookies that haven't expired
await fs.writeFile(this.file, JSON.stringify([...this.cookiesValid(false)]));
}
};

View File

@ -1,11 +1,5 @@
import url from "url";
class CookieParseError extends Error {
constructor(...args) {
super(...args);
this.name = "CookieParseError";
}
}
import {paramError, CookieParseError} from "./errors.mjs";
const validateHostname = (cookieHostname, requestHostname, subdomains) => {
cookieHostname = cookieHostname.toLowerCase();
@ -37,12 +31,17 @@ const splitN = (str, sep, n) => {
export default class Cookie {
constructor(str, requestURL) {
if(typeof str !== "string")
throw new TypeError("First parameter is not a string!");
throw paramError("First", "str", "new Cookie()", "string");
if(typeof requestURL !== "string")
throw paramError("Second", "requestURL", "new Cookie()", "string");
// check if url is valid
new url.URL(requestURL);
const splitted = str.split("; ");
[this.name, this.value] = splitN(splitted[0], "=", 1);
if(!this.name)
throw new CookieParseError("Invalid cookie name \"" + this.name + "\"");
throw new CookieParseError("Invalid cookie name \"" + this.name + "\"!");
if(this.value.startsWith("\"") && this.value.endsWith("\""))
this.value = this.value.slice(1, -1);
@ -56,7 +55,8 @@ export default class Cookie {
if(this.expiry) // max-age has precedence over expires
continue;
if(!/^(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun), \d{2}[ -](?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[ -]\d{2,4} \d{2}:\d{2}:\d{2} GMT$/.test(v)
|| (this.expiry = new Date(v)) === "Invalid Date")
|| (this.expiry = new Date(v)).toString() === "Invalid Date"
|| this.expiry.getTime() < 0)
throw new CookieParseError("Invalid value for Expires \"" + v + "\"!");
}
else if(k === "max-age") {
@ -93,7 +93,7 @@ export default class Cookie {
if(this.name.toLowerCase().startsWith("__secure-") && (!this.secure || parsedURL.protocol !== "https:"))
throw new CookieParseError("Cookie has \"__Secure-\" prefix but \"Secure\" isn't set or the cookie is not set via https!");
if(this.name.toLowerCase().startsWith("__host-") && (!this.secure || parsedURL.protocol !== "https:" || this.domain || (this.path && this.path !== "/")))
if(this.name.toLowerCase().startsWith("__host-") && (!this.secure || parsedURL.protocol !== "https:" || this.domain || this.path !== "/"))
throw new CookieParseError("Cookie has \"__Host-\" prefix but \"Secure\" isn't set, the cookie is not set via https, \"Domain\" is set or \"Path\" is not equal to \"/\"!");
// assign defaults

12
src/errors.mjs Normal file
View File

@ -0,0 +1,12 @@
export class CookieParseError extends Error {
constructor(...args) {
super(...args);
this.name = "CookieParseError";
}
};
export function paramError(position, paramName, functionName, validTypes) {
validTypes = [validTypes].flat().map(t => "\"" + t + "\"");
validTypes = validTypes.slice(0, -1).join(", ") + (validTypes.length > 1 ? " or " : "") + validTypes.slice(-1);
return new TypeError(`${position} parameter "${paramName}" passed to "${functionName}" is not of type ${validTypes}!`);
};

View File

@ -1,13 +1,12 @@
import fetch from "flumm-fetch";
import _fetch from "flumm-fetch";
import CookieJar from "./cookie-jar.mjs";
import Cookie from "./cookie.mjs";
const cookieJar = new CookieJar();
export default async function cookieFetch(url, options) {
export default async function fetch(url, options) {
let cookies = "";
[...cookieJar.cookiesValidForRequest(url)]
.filter((v, i, a) => a.slice(0, i).every(c => c.name !== v.name)) // filter cookies with duplicate names
.forEach(c => cookies += c.serialize() + "; ");
if(cookies) {
@ -30,3 +29,4 @@ export default async function cookieFetch(url, options) {
}
export {cookieJar, CookieJar, Cookie};

285
test/cookie.mjs Normal file
View File

@ -0,0 +1,285 @@
import Cookie from "../src/cookie.mjs";
import {CookieParseError} from "../src/errors.mjs";
export default Test => [
new Test("cookie parser", () => {
const inputs = [
[ // type error
123,
""
],
[ // type error
"id=a3fWa",
123
],
[ // type error invalid url
"id=a3fWa",
""
],
[ // type error invalid url
"id=a3fWa",
"github.com"
],
// TODO: fix this test case, parser shouldn't allow it. it is currently ignored
[ // type error invalid url
"id=a3fWa",
"https:abc/abs"
],
[ // cookie parse error invalid cookie name
"",
"https://github.com"
],
[ // cookie parse error invalid value for expires
"id=a3fWa; Expires=Wed, 21 Oct 2015 07:28: GMT; Secure; HttpOnly",
"https://github.com"
],
[ // cookie parse error invalid value for expires
"id=a3fWa; Expires=Wed, 21 Onv 2015 07:28:00 GMT; Secure; HttpOnly",
"https://github.com"
],
[ // cookie parse error invalid value for expires
"id=a3fWa; Expires=Wed, 21 Oct 20151 07:28:00 GMT; Secure; HttpOnly",
"https://github.com"
],
[ // cookie parse error invalid value for expires
"id=a3fWa; Expires=Wed, 32 Oct 2015 07:28:00 GMT; Secure; HttpOnly",
"https://github.com"
],
[ // cookie parse error invalid value for expires
"id=a3fWa; Expires=Wed, 21 Oct 2015 25:28:00 GMT; Secure; HttpOnly",
"https://github.com"
],
[ // cookie parse error invalid value for expires
"id=a3fWa; Expires=Wed, 21 Oct 2015 07:61:00 GMT; Secure; HttpOnly",
"https://github.com"
],
[ // cookie parse error invalid value for expires
"id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 UTC; Secure; HttpOnly",
"https://github.com"
],
[ // cookie parse error invalid value for expires
"id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT+2; Secure; HttpOnly",
"https://github.com"
],
[ // cookie parse error invalid value for expires
"id=a3fWa; Expires=San, 21 Onv 2015 07:28:00 GMT; Secure; HttpOnly",
"https://github.com"
],
[ // cookie parse error invalid value for expires
"id=a3fWa; Expires=Wed, 31 Dec 1969 07:28:00 GMT; Secure; HttpOnly",
"https://github.com"
],
[ // cookie parse error invalid value for expires
"id=a3fWa; Max-Age=121252a; Secure; HttpOnly",
"https://github.com"
],
[ // cookie parse error invalid key secur
"id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secur; HttpOnly",
"https://github.com"
],
[ // cookie parse error invalid key HttpOly with value 2
"id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOly=2",
"https://github.com"
],
[ // cookie parse error not set via https
"__Secure-id=a3fWa; Expires=Wed, 21 Nov 2015 07:28:00 GMT; Secure; HttpOnly",
"http://github.com"
],
[ // cookie parse error secure not set
"__Secure-id=a3fWa; Expires=Wed, 21 Nov 2015 07:28:00 GMT; HttpOnly",
"https://github.com"
],
[ // cookie parse error secure not set
"__Host-id=a3fWa; Expires=Wed, 21 Nov 2015 07:28:00 GMT; HttpOnly; Path=/",
"https://github.com"
],
[ // cookie parse error not set via https
"__Host-id=a3fWa; Expires=Wed, 21 Nov 2015 07:28:00 GMT; Secure; HttpOnly; Path=/",
"http://github.com"
],
[ // cookie parse error domain is set
"__Host-id=a3fWa; Expires=Wed, 21 Nov 2015 07:28:00 GMT; Secure; HttpOnly; Domain=github.com; Path=/",
"https://github.com"
],
[ // cookie parse error path is not set
"__Host-id=a3fWa; Expires=Wed, 21 Nov 2015 07:28:00 GMT; Secure; HttpOnly",
"https://github.com"
],
[ // cookie parse error path is not equal to /
"__Host-id=a3fWa; Expires=Wed, 21 Nov 2015 07:28:00 GMT; Secure; HttpOnly; Path=/lel/",
"https://github.com"
],
[ // cookie parse error domain is not a subdomain
"id=a3fWa; Expires=Wed, 21 Nov 2015 07:28:00 GMT; Domain=github.com",
"https://gist.github.com"
],
[ // cookie parse error domain is not a subdomain
"id=a3fWa; Expires=Wed, 21 Nov 2015 07:28:00 GMT; Domain=npmjs.com",
"https://gist.github.com"
],
[ // success
"__Secure-id=a3fWa; Expires=Wed, 21 Nov 2015 07:28:00 GMT; Secure; HttpOnly",
"https://github.com"
],
[ // success
"__Host-id=a3fWa; Expires=Wed, 21 Nov 2015 07:28:00 GMT; Secure; HttpOnly; Path=/",
"https://github.com"
],
[ // success
"__Host-id=a3fWa; Expires=Wed, 21 Nov 2099 20:28:33 GMT; Secure; HttpOnly; Path=/",
"https://github.com"
],
[ // success
"id=a3fWa; Expires=Wed, 21 Nov 2015 07:28:00 GMT; Secure; HttpOnly; Path=/lul/; Domain=.usercontent.github.com",
"https://github.com"
],
[ // success
"id=a3fWa; Expires=Wed, 21 Nov 2015 07:28:00 GMT; Secure; HttpOnly; SameSite=Strict; Path=/lul/; Domain=usercontent.github.com",
"https://github.com"
],
[ // success max-age takes precendence over expires
"id=a3fWa; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=1000",
"https://github.com"
],
[ // success max-age takes precendence over expires
"id=a3fWa; Max-Age=1000; Expires=Thu, 01 Jan 1970 00:00:00 GMT",
"https://github.com"
],
];
const catchErrorTest = (input, catchFnc) => {
try {
new Cookie(...input);
return false;
}
catch(error) {
return catchFnc(error);
}
};
const catchErrorTypeMessageTest = (input, type, message) => catchErrorTest(input, e => e instanceof type && e.message === message);
const compareCookieProps = (input, expiryFnc, properties) => {
const cookie = new Cookie(...input);
return Object.entries(properties).every(([prop, value]) => cookie[prop] === value)
&& expiryFnc(cookie.expiry);
};
return inputs.slice(0, 3).every(input => catchErrorTest(input, e => e instanceof TypeError))
// cookies[4] is the test case that is ignored for now
&& catchErrorTypeMessageTest(inputs[5], CookieParseError, "Invalid cookie name \"\"!")
&& catchErrorTypeMessageTest(inputs[6], CookieParseError, "Invalid value for Expires \"Wed, 21 Oct 2015 07:28: GMT\"!")
&& catchErrorTypeMessageTest(inputs[7], CookieParseError, "Invalid value for Expires \"Wed, 21 Onv 2015 07:28:00 GMT\"!")
&& catchErrorTypeMessageTest(inputs[8], CookieParseError, "Invalid value for Expires \"Wed, 21 Oct 20151 07:28:00 GMT\"!")
&& catchErrorTypeMessageTest(inputs[9], CookieParseError, "Invalid value for Expires \"Wed, 32 Oct 2015 07:28:00 GMT\"!")
&& catchErrorTypeMessageTest(inputs[10], CookieParseError, "Invalid value for Expires \"Wed, 21 Oct 2015 25:28:00 GMT\"!")
&& catchErrorTypeMessageTest(inputs[11], CookieParseError, "Invalid value for Expires \"Wed, 21 Oct 2015 07:61:00 GMT\"!")
&& catchErrorTypeMessageTest(inputs[12], CookieParseError, "Invalid value for Expires \"Wed, 21 Oct 2015 07:28:00 UTC\"!")
&& catchErrorTypeMessageTest(inputs[13], CookieParseError, "Invalid value for Expires \"Wed, 21 Oct 2015 07:28:00 GMT+2\"!")
&& catchErrorTypeMessageTest(inputs[14], CookieParseError, "Invalid value for Expires \"San, 21 Onv 2015 07:28:00 GMT\"!")
&& catchErrorTypeMessageTest(inputs[15], CookieParseError, "Invalid value for Expires \"Wed, 31 Dec 1969 07:28:00 GMT\"!")
&& catchErrorTypeMessageTest(inputs[16], CookieParseError, "Invalid value for Max-Age \"121252a\"!")
&& catchErrorTypeMessageTest(inputs[17], CookieParseError, "Invalid key \"secur\" specified!")
&& catchErrorTypeMessageTest(inputs[18], CookieParseError, "Invalid key \"httpoly\" with value \"2\" specified!")
&& inputs.slice(19, 21).every(input => catchErrorTypeMessageTest(input, CookieParseError, "Cookie has \"__Secure-\" prefix but \"Secure\" isn't set or the cookie is not set via https!"))
&& inputs.slice(21, 26).every(input => catchErrorTypeMessageTest(input, CookieParseError, "Cookie has \"__Host-\" prefix but \"Secure\" isn't set, the cookie is not set via https, \"Domain\" is set or \"Path\" is not equal to \"/\"!"))
&& catchErrorTypeMessageTest(inputs[26], CookieParseError, "Invalid value for Domain \"github.com\": cookie was received from \"gist.github.com\"!")
&& catchErrorTypeMessageTest(inputs[27], CookieParseError, "Invalid value for Domain \"npmjs.com\": cookie was received from \"gist.github.com\"!")
&& compareCookieProps(
inputs[28],
exp => exp.getTime() === new Date("Wed, 21 Nov 2015 07:28:00 GMT").getTime(),
{
name: "__Secure-id",
value: "a3fWa",
secure: true,
domain: "github.com",
subdomains: false,
path: "/"
}
)
&& compareCookieProps(
inputs[29],
exp => exp.getTime() === new Date("Wed, 21 Nov 2015 07:28:00 GMT").getTime(),
{
name: "__Host-id",
value: "a3fWa",
secure: true,
domain: "github.com",
subdomains: false,
path: "/"
}
)
&& compareCookieProps(
inputs[30],
exp => exp.getTime() === new Date("Wed, 21 Nov 2099 20:28:33 GMT").getTime(),
{
name: "__Host-id",
value: "a3fWa",
secure: true,
domain: "github.com",
subdomains: false,
path: "/"
}
)
&& compareCookieProps(
inputs[31],
exp => exp.getTime() === new Date("Wed, 21 Nov 2015 07:28:00 GMT").getTime(),
{
name: "id",
value: "a3fWa",
secure: true,
domain: "usercontent.github.com",
subdomains: true,
path: "/lul/"
}
)
&& compareCookieProps(
inputs[32],
exp => exp.getTime() === new Date("Wed, 21 Nov 2015 07:28:00 GMT").getTime(),
{
name: "id",
value: "a3fWa",
secure: true,
domain: "usercontent.github.com",
subdomains: true,
path: "/lul/"
}
)
&& compareCookieProps(
inputs[33],
exp => exp.getTime() > new Date("Thu, 01 Jan 1970 00:00:00 GMT").getTime(),
{
name: "id",
value: "a3fWa",
secure: false,
domain: "github.com",
subdomains: false,
path: "/"
}
)
&& compareCookieProps(
inputs[34],
exp => exp.getTime() > new Date("Thu, 01 Jan 1970 00:00:00 GMT").getTime(),
{
name: "id",
value: "a3fWa",
secure: false,
domain: "github.com",
subdomains: false,
path: "/"
}
);
})
];

27
test/errors.mjs Normal file
View File

@ -0,0 +1,27 @@
import {CookieParseError, paramError} from "../src/errors.mjs";
export default Test => [
new Test("function paramError", () => {
const position = "something";
const paramName = "some_param";
const functionName = "some_func";
const validTypes = ["lol", "lel", "lul", "lal"];
const errors = [
paramError(position, paramName, functionName, validTypes[0]),
paramError(position, paramName, functionName, validTypes.slice(0, 2)),
paramError(position, paramName, functionName, validTypes)
];
return errors.every(e => e instanceof TypeError)
&& errors.every(e => e.name === "TypeError")
&& errors[0].message === "something parameter \"some_param\" passed to \"some_func\" is not of type \"lol\"!"
&& errors[1].message === "something parameter \"some_param\" passed to \"some_func\" is not of type \"lol\" or \"lel\"!"
&& errors[2].message === "something parameter \"some_param\" passed to \"some_func\" is not of type \"lol\", \"lel\", \"lul\" or \"lal\"!";
}),
new Test("class CookieParseError", () => {
const message = "this is a test error";
const error = new CookieParseError(message);
return error instanceof CookieParseError
&& error.name === "CookieParseError"
&& error.message === message;
})
];

45
test/index.mjs Normal file
View File

@ -0,0 +1,45 @@
import cookie from "./cookie.mjs";
import errors from "./errors.mjs";
class Test {
constructor(name, fnc) {
this.name = name
this.fnc = fnc
}
async runTest() {
return this.fnc();
}
}
const tests = [
cookie,
errors
].flatMap(t => t(Test));
(async () => {
console.log("running tests...");
const testResults = await Promise.all(tests.map(async t => {
try {
t.result = await t.runTest();
if(typeof t.result !== "boolean") {
t.result = false;
console.error("test did not return a boolean: " + t.name);
}
}
catch(error) {
console.error("uncaught error in test: " + t.name + "\n", error);
t.result = false;
}
return t;
}));
testResults.forEach(t => {
if(t.result)
console.info("✔ " + t.name);
else
console.warn("✘ " + t.name);
});
const succeededTests = testResults.map(t => t.result).reduce((a, b) => a + b);
const success = succeededTests === testResults.length;
(success ? console.info : console.warn)((success ? "✔" : "✘") + " " + succeededTests + "/" + testResults.length + " tests successful");
!success && process.exit(1);
})();