feat: port to typescript

* Wrapper was showing it's age, dated syntax, zero tests, et cetera.
* Ported wrapper to Typescript.
* Actually, was a full rewrite, the original interfacing was too obscure.
* Added linting (Airbnb).
* Rewrite includes full feature parity with the Holiday API service.
* This includes support for additional endpoints and latest request parameters.
* Added a ton of additional examples to the README.
* Finally got testing into the mix.
* Added Travis for CI and Coveralls for coverage reporting.
This commit is contained in:
Josh Sherman 2019-08-29 00:08:51 -05:00
parent b6ad36a075
commit fb903a8532
No known key found for this signature in database
GPG key ID: 55B058A80530EF22
21 changed files with 7096 additions and 111 deletions

31
.eslintrc.json Normal file
View file

@ -0,0 +1,31 @@
{
"env": {
"browser": true,
"es6": true,
"jest/globals": true,
"node": true
},
"extends": [
"airbnb-base",
"plugin:import/typescript"
],
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 2018,
"sourceType": "module"
},
"plugins": [
"@typescript-eslint",
"jest"
],
"rules": {
"@typescript-eslint/no-unused-vars": "error",
"import/prefer-default-export": "off",
"lines-between-class-members": "off",
"no-unused-vars": "off"
}
}

37
.gitignore vendored
View file

@ -1,37 +1,2 @@
# Logs
logs
*.log
npm-debug.log*
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules node_modules
jspm_packages npm-debug.log*
# Optional npm cache directory
.npm
# Optional REPL history
.node_repl_history

2
.npmignore Normal file
View file

@ -0,0 +1,2 @@
tsconfig.json
src

8
.travis.yml Normal file
View file

@ -0,0 +1,8 @@
language: node_js
node_js:
- 7
- 8
- 9
- 10
- 11
- 12

View file

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2016 Josh Sherman Copyright (c) 2016, 2017, 2018, 2019 Gravity Boulevard, LLC
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

175
README.md
View file

@ -1,33 +1,176 @@
# node-holidayapi # node-holidayapi
Official Node.js library for [Holiday API](https://holidayapi.com) Official Node.js library for [Holiday API](https://holidayapi.com)
[![License](https://img.shields.io/npm/l/node-holidayapi?style=for-the-badge)](https://github.com/holidayapi/node-holidayapi/blob/master/LICENSE)
![Node Version](https://img.shields.io/node/v/node-holidayapi?style=for-the-badge)
![Build Status](https://img.shields.io/travis/holidayapi/node-holidayapi/master?style=for-the-badge)
[![Coverage Status](https://img.shields.io/coveralls/github/holidayapi/node-holidayapi/master?style=for-the-badge)](https://coveralls.io/github/holidayapi/node-holidayapi?branch=master)
## Installation ## Installation
```shell ```shell
# NPM
npm install --save node-holidayapi npm install --save node-holidayapi
# Yarn
yarn add node-holidayapi
``` ```
## Usage ## Usage
```javascript ```javascript
var HolidayAPI = require('node-holidayapi'); import { HolidayAPI } from 'node-holidayapi';
var hapi = new HolidayAPI('_YOUR_API_KEY_').v1;
var parameters = { const key = 'Insert your API key here';
// Required const holidayApi = new HolidayAPI({ key });
// Fetch supported countries and subdivisions
holidayApi.countries()
.then((countries) => { console.log(countries); })
.catch((err) => { console.error(err); });
// Fetch supported languages
holidayApi.languages();
.then((languages) => { console.log(languages); })
.catch((err) => { console.error(err); });
// Fetch holidays with minimum parameters
holidayApi.holidays({ country: 'US', year: 2019 });
.then((holidays) => { console.log(holidays); })
.catch((err) => { console.error(err); });
// Async? Await? No problem!
(async () => {
// Fetch supported countries and subdivisions
const countries = await holidayApi.countries();
// Fetch supported languages
const languages = await holidayApi.languages();
// Fetch holidays with minimum parameters
const holidays = await holidayApi.holidays({
country: 'US',
year: 2019,
});
})();
```
## Examples
### Fetch holidays for a specific year
```javascript
holidayApi.holidays({
country: 'US', country: 'US',
year: 2016, year: 2019,
// Optional
// month: 7,
// day: 4,
// previous: true,
// upcoming: true,
// public: true,
// pretty: true,
};
hapi.holidays(parameters, function (err, data) {
// Insert awesome code here...
}); });
``` ```
### Fetch holidays for a specific month
```javascript
holidayApi.holidays({
country: 'US',
year: 2019,
month: 7,
});
```
### Fetch holidays for a specific day
```javascript
holidayApi.holidays({
country: 'US',
year: 2019,
month: 7,
day: 4,
});
```
### Fetch upcoming holidays based on a specific date
```javascript
holidayApi.holidays({
country: 'US',
year: 2019,
month: 7,
day: 4,
upcoming: true,
});
```
### Fetch previous holidays based on a specific date
```javascript
holidayApi.holidays({
country: 'US',
year: 2019,
month: 7,
day: 4,
previous: true,
});
```
### Fetch only public holidays
```javascript
holidayApi.holidays({
country: 'US',
year: 2019,
public: true,
});
```
### Fetch holidays for a specific subdivision
```javascript
holidayApi.holidays({
country: 'GB-ENG',
year: 2019,
});
```
### Include subdivision holidays with countrywide holidays
```javascript
holidayApi.holidays({
country: 'US',
year: 2019,
subdivisions: true,
});
```
### Search for a holiday by name
```javascript
holidayApi.holidays({
country: 'US',
year: 2019,
search: 'New Year',
});
```
### Translate holidays to another language
```javascript
holidayApi.holidays({
country: 'US',
year: 2019,
language: 'zh', // Chinese (Simplified)
});
```
### Fetch holidays for multiple countries
```javascript
holidayApi.holidays({
country: 'US,GB,NZ',
year: 2019,
});
holidayApi.holidays({
country: ['US', 'GB', 'NZ'],
year: 2019,
});
```

14
dist/holidayapi.d.ts vendored Normal file
View file

@ -0,0 +1,14 @@
import { CountriesResponse, HolidaysResponse, HolidaysRequest, LanguagesResponse, Request } from './types';
export declare class HolidayAPI {
baseUrl: string;
key: string;
constructor({ key, version }: {
key?: string;
version?: number;
});
private createUrl;
private request;
countries(request?: Request): Promise<CountriesResponse>;
holidays(request?: HolidaysRequest): Promise<HolidaysResponse>;
languages(request?: Request): Promise<LanguagesResponse>;
}

136
dist/holidayapi.js vendored Normal file
View file

@ -0,0 +1,136 @@
"use strict";
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
Object.defineProperty(exports, "__esModule", { value: true });
var node_fetch_1 = require("node-fetch");
var url_1 = require("url");
var HolidayAPI = (function () {
function HolidayAPI(_a) {
var key = _a.key, _b = _a.version, version = _b === void 0 ? 1 : _b;
var getYours = 'get yours at HolidayAPI.com';
var uuidRegExp = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/;
if (!key) {
throw new Error("Missing API key, " + getYours);
}
if (!uuidRegExp.test(key)) {
throw new Error("Invalid API key, " + getYours);
}
if (version !== 1) {
throw new Error('Invalid version number, expected "1"');
}
this.baseUrl = "https://holidayapi.com/v" + version + "/";
this.key = key;
}
HolidayAPI.prototype.createUrl = function (endpoint, request) {
var parameters = __assign({ key: this.key }, request);
var url = new url_1.URL(endpoint, this.baseUrl);
url.search = new url_1.URLSearchParams(parameters).toString();
return url.toString();
};
HolidayAPI.prototype.request = function (endpoint, request) {
return __awaiter(this, void 0, void 0, function () {
var response, payload, err_1;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4, node_fetch_1.default(this.createUrl(endpoint, request))];
case 1:
response = _a.sent();
_a.label = 2;
case 2:
_a.trys.push([2, 4, , 5]);
return [4, response.json()];
case 3:
payload = _a.sent();
return [3, 5];
case 4:
err_1 = _a.sent();
payload = {};
return [3, 5];
case 5:
if (!response.ok) {
throw new Error(payload.error || response.statusText);
}
return [2, payload];
}
});
});
};
HolidayAPI.prototype.countries = function (request) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
return [2, this.request('countries', request)];
});
});
};
HolidayAPI.prototype.holidays = function (request) {
if (request === void 0) { request = {}; }
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
if (!request.country) {
throw new Error('Missing country');
}
else if (!request.year) {
throw new Error('Missing year');
}
else if (request.previous && request.upcoming) {
throw new Error('Previous and upcoming are mutually exclusive');
}
return [2, this.request('holidays', request)];
});
});
};
HolidayAPI.prototype.languages = function (request) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
return [2, this.request('languages', request)];
});
});
};
return HolidayAPI;
}());
exports.HolidayAPI = HolidayAPI;

2
dist/index.d.ts vendored Normal file
View file

@ -0,0 +1,2 @@
export * from './types';
export * from './holidayapi';

6
dist/index.js vendored Normal file
View file

@ -0,0 +1,6 @@
"use strict";
function __export(m) {
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
}
Object.defineProperty(exports, "__esModule", { value: true });
__export(require("./holidayapi"));

64
dist/types.d.ts vendored Normal file
View file

@ -0,0 +1,64 @@
export declare type Endpoint = 'countries' | 'holidays' | 'languages';
export declare type Request = {
key?: string;
format?: 'csv' | 'json' | 'php' | 'tsv' | 'yaml' | 'xml';
pretty?: boolean;
};
export declare type Requests = Request | HolidaysRequest;
export declare type HolidaysRequest = Request & {
country?: string;
year?: number;
day?: number;
month?: number;
language?: string;
previous?: boolean;
public?: boolean;
search?: string;
subdivisions?: boolean;
upcoming?: boolean;
};
export declare type Response = {
requests: {
available: number;
resets: Date;
used: number;
};
status: number;
error?: string;
};
export declare type Responses = (CountriesResponse | HolidaysResponse | LanguagesResponse);
export declare type CountriesResponse = Response & {
countries?: {
code: string;
codes: {
'alpha-2': string;
'alpha-3': string;
numeric: string;
};
flag: string;
languages: string[];
name: string;
subdivisions: {
code: string;
languages: string[];
name: string;
}[];
}[];
};
export declare type HolidaysResponse = Response & {
holidays?: {
country: string;
date: Date;
name: string;
observed: Date;
public: boolean;
uuid: string;
subdivisions?: string[];
}[];
};
export declare type LanguagesResponse = Response & {
languages?: {
code: string;
name: string;
}[];
};

2
dist/types.js vendored Normal file
View file

@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });

View file

@ -1,49 +0,0 @@
'use strict';
const https = require('https');
const qs = require('querystring');
var HolidayAPI = function (key) {
if ('undefined' !== typeof key) {
HolidayAPI.prototype.key = key;
}
};
HolidayAPI.prototype.v1 = {};
HolidayAPI.prototype.v1.holidays = function (parameters, callback) {
const querystringObject = Object.assign(
{},
{key: HolidayAPI.prototype.key},
parameters,
)
const querystring = qs.stringify(querystringObject);
const url = `https://holidayapi.com/v1/holidays?${querystring}`;
https.get(url, function (res) {
res.on('data', function (data) {
try {
data = JSON.parse(data);
} catch (e) {
data = {};
}
var error = null;
if (res.statusCode !== 200) {
if ('undefined' === typeof data['error']) {
error = 'Unknown error.';
} else {
error = data.error;
}
}
return callback(error, data);
});
}).on('error', function (e) {
callback(e.message);
});
};
module.exports = HolidayAPI;

4
jest.config.js Normal file
View file

@ -0,0 +1,4 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
};

6141
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,20 +1,49 @@
{ {
"name": "node-holidayapi", "name": "node-holidayapi",
"version": "1.0.2", "version": "2.0.0",
"description": "Official Node.js library for Holiday API", "description": "Official Node.js library for Holiday API",
"main": "index.js", "main": "dist/index.js",
"scripts": { "types": "dist/index.d.ts",
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/joshtronic/node-holidayapi.git" "url": "git+https://github.com/holidayapi/node-holidayapi.git"
}, },
"keywords": [ "holiday", "holidays", "holidayapi" ], "keywords": [
"calendar",
"holiday",
"holidays",
"holidayapi"
],
"author": "Josh Sherman <hello@holidayapi.com> (https://holidayapi.com)", "author": "Josh Sherman <hello@holidayapi.com> (https://holidayapi.com)",
"license": "MIT", "license": "MIT",
"bugs": { "bugs": {
"url": "https://github.com/joshtronic/node-holidayapi/issues" "url": "https://github.com/holidayapi/node-holidayapi/issues"
}, },
"homepage": "https://holidayapi.com" "homepage": "https://holidayapi.com",
"engines": {
"node": ">= 7.0.0"
},
"devDependencies": {
"@types/jest": "^24.0.18",
"@types/nock": "^10.0.3",
"@types/node": "^12.7.4",
"@types/node-fetch": "^2.5.0",
"@typescript-eslint/eslint-plugin": "^2.2.0",
"@typescript-eslint/parser": "^2.2.0",
"coveralls": "^3.0.6",
"eslint": "^6.3.0",
"eslint-config-airbnb-base": "^14.0.0",
"eslint-plugin-import": "^2.18.2",
"eslint-plugin-jest": "^22.17.0",
"jest": "^24.9.0",
"nock": "^10.0.6",
"ts-jest": "^24.0.2",
"typescript": "^3.6.2"
},
"dependencies": {
"node-fetch": "^2.6.0"
},
"scripts": {
"test": "jest --coverage --coverageReporters=text-lcov | coveralls"
}
} }

83
src/holidayapi.ts Normal file
View file

@ -0,0 +1,83 @@
/**
* Copyright (c) Gravity Boulevard, LLC
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import fetch from 'node-fetch';
import { URL, URLSearchParams } from 'url';
import {
CountriesResponse, Endpoint, HolidaysResponse, HolidaysRequest,
LanguagesResponse, Request, Requests, Responses,
} from './types';
export class HolidayAPI {
baseUrl: string;
key: string;
constructor({ key, version = 1 }: { key?: string, version?: number }) {
const getYours = 'get yours at HolidayAPI.com';
const uuidRegExp = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/;
if (!key) {
throw new Error(`Missing API key, ${getYours}`);
}
if (!uuidRegExp.test(key)) {
throw new Error(`Invalid API key, ${getYours}`);
}
if (version !== 1) {
throw new Error('Invalid version number, expected "1"');
}
this.baseUrl = `https://holidayapi.com/v${version}/`;
this.key = key;
}
private createUrl(endpoint: Endpoint, request?: Requests): string {
const parameters = { key: this.key, ...request } as any;
const url = new URL(endpoint, this.baseUrl);
url.search = new URLSearchParams(parameters).toString();
return url.toString();
}
private async request(endpoint: Endpoint, request?: Requests): Promise<Responses> {
const response = await fetch(this.createUrl(endpoint, request));
let payload;
try {
payload = await response.json();
} catch (err) {
payload = {};
}
if (!response.ok) {
throw new Error(payload.error || response.statusText);
}
return payload;
}
async countries(request?: Request): Promise<CountriesResponse> {
return this.request('countries', request);
}
async holidays(request: HolidaysRequest = {}): Promise<HolidaysResponse> {
if (!request.country) {
throw new Error('Missing country');
} else if (!request.year) {
throw new Error('Missing year');
} else if (request.previous && request.upcoming) {
throw new Error('Previous and upcoming are mutually exclusive');
}
return this.request('holidays', request);
}
async languages(request?: Request): Promise<LanguagesResponse> {
return this.request('languages', request);
}
}

9
src/index.ts Normal file
View file

@ -0,0 +1,9 @@
/**
* Copyright (c) Gravity Boulevard, LLC
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
export * from './types';
export * from './holidayapi';

81
src/types.ts Normal file
View file

@ -0,0 +1,81 @@
/**
* Copyright (c) Gravity Boulevard, LLC
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
export type Endpoint = 'countries' | 'holidays' | 'languages';
export type Request = {
key?: string,
format?: 'csv' | 'json' | 'php' | 'tsv' | 'yaml' | 'xml',
pretty?: boolean,
};
export type Requests = Request | HolidaysRequest;
export type HolidaysRequest = Request & {
country?: string,
year?: number,
day?: number,
month?: number,
language?: string,
previous?: boolean,
public?: boolean,
search?: string,
subdivisions?: boolean,
upcoming?: boolean,
};
export type Response = {
requests: {
available: number,
resets: Date,
used: number,
},
status: number,
error?: string,
};
export type Responses = (
CountriesResponse | HolidaysResponse | LanguagesResponse
);
export type CountriesResponse = Response & {
countries?: {
code: string,
codes: {
'alpha-2': string,
'alpha-3': string,
numeric: string,
},
flag: string,
languages: string[],
name: string,
subdivisions: {
code: string,
languages: string[],
name: string,
}[],
}[],
};
export type HolidaysResponse = Response & {
holidays?: {
country: string,
date: Date,
name: string,
observed: Date,
public: boolean,
uuid: string,
subdivisions?: string[],
}[],
};
export type LanguagesResponse = Response & {
languages?: {
code: string,
name: string,
}[],
};

299
tests/holidayapi.test.ts Normal file
View file

@ -0,0 +1,299 @@
import * as nock from 'nock';
import { HolidayAPI } from '../src/holidayapi';
const baseUrl = 'https://holidayapi.com/v1/';
const key = 'b58e6dec-8a47-459f-a3c1-eaa26eb4dd30';
describe('holidayapi', () => {
describe('instantiation', () => {
it('should error when key is missing', () => {
expect.assertions(1);
expect(() => {
const holidayapi = new HolidayAPI({});
expect(holidayapi.key).toBeUndefined();
}).toThrowError(/missing api key/i);
});
it('should error when key is invalid format', () => {
expect.assertions(1);
expect(() => {
const holidayapi = new HolidayAPI({
key: 'zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz',
});
expect(holidayapi.key).toBeUndefined();
}).toThrowError(/invalid api key/i);
});
it('should error when version is too low', () => {
expect.assertions(1);
expect(() => {
const holidayapi = new HolidayAPI({ key, version: 0 });
expect(holidayapi.baseUrl).toBeUndefined();
}).toThrowError(/invalid version/i);
});
it('should error when version is too high', () => {
expect.assertions(1);
expect(() => {
const holidayapi = new HolidayAPI({ key, version: 2 });
expect(holidayapi.baseUrl).toBeUndefined();
}).toThrowError(/invalid version/i);
});
it('should assign class members', () => {
const holidayapi = new HolidayAPI({ key });
expect(holidayapi.baseUrl).toBe(baseUrl);
expect(holidayapi.key).toBe(key);
});
});
describe('v1 requests', () => {
const holidayapi = new HolidayAPI({ key });
const mockRequest = nock(baseUrl);
describe('/v1/countries', () => {
const basePath = `/countries?key=${key}`;
it('should return countries', async () => {
const expectedResponse = {
status: 200,
requests: {
used: 1000,
available: 9000,
resets: '2019-10-01 00:00:00',
},
countries: [
{
code: 'ST',
name: 'Sao Tome and Principe',
languages: ['pt'],
codes: {
'alpha-2': 'ST',
'alpha-3': 'STP',
numeric: 678,
},
flag: 'https://www.countryflags.io/ST/flat/64.png',
subdivisions: [
{
code: 'ST-P',
name: 'Príncipe',
languages: ['pt'],
},
{
code: 'ST-S',
name: 'São Tomé',
languages: ['pt'],
},
],
},
],
};
mockRequest.get(basePath).reply(200, expectedResponse);
expect(await holidayapi.countries()).toStrictEqual(expectedResponse);
});
it('should raise 4xx errors', async () => {
const expectedResponse = {
status: 429,
error: 'Rate limit exceeded',
};
expect.assertions(1);
mockRequest.get(basePath).reply(429, expectedResponse);
try {
await holidayapi.countries();
} catch (err) {
expect(err.message).toMatch(/rate limit exceeded/i);
}
});
it('should raise 5xx errors', async () => {
expect.assertions(1);
mockRequest.get(basePath).reply(500);
try {
await holidayapi.countries();
} catch (err) {
expect(err.message).toMatch(/internal server error/i);
}
});
});
describe('/v1/holidays', () => {
const basePath = `/holidays?key=${key}`;
it('should return holidays', async () => {
const expectedResponse = {
status: 200,
requests: {
used: 1000,
available: 9000,
resets: '2019-10-01 00:00:00',
},
holidays: [
{
name: 'Independence Day',
date: '2015-07-04',
observed: '2015-07-03',
public: true,
country: 'US',
uuid: '88268759-9b90-468c-804f-b729b8418e7c',
weekday: {
date: {
name: 'Saturday',
numeric: '6',
},
observed: {
name: 'Friday',
numeric: '5',
},
},
},
],
};
mockRequest.get(`${basePath}&country=US&year=2019&month=7&day=4`)
.reply(200, expectedResponse);
expect(await holidayapi.holidays({
country: 'US',
year: 2019,
month: 7,
day: 4,
})).toStrictEqual(expectedResponse);
});
it('should error when country is missing', async () => {
expect.assertions(1);
try {
await holidayapi.holidays();
} catch (err) {
expect(err.message).toMatch(/missing country/i);
}
});
it('should error when year is missing', async () => {
expect.assertions(1);
try {
await holidayapi.holidays({ country: 'US' });
} catch (err) {
expect(err.message).toMatch(/missing year/i);
}
});
it('should error when both previous and upcoming', async () => {
expect.assertions(1);
try {
await holidayapi.holidays({
country: 'US',
year: 2019,
previous: true,
upcoming: true,
});
} catch (err) {
expect(err.message).toMatch(/previous and upcoming/i);
}
});
it('should raise 4xx errors', async () => {
const expectedResponse = {
status: 429,
error: 'Rate limit exceeded',
};
expect.assertions(1);
mockRequest.get(`${basePath}&country=US&year=2019`)
.reply(429, expectedResponse);
try {
await holidayapi.holidays({ country: 'US', year: 2019 });
} catch (err) {
expect(err.message).toMatch(/rate limit exceeded/i);
}
});
it('should raise 5xx errors', async () => {
expect.assertions(1);
mockRequest.get(`${basePath}&country=US&year=2019`).reply(500);
try {
await holidayapi.holidays({ country: 'US', year: 2019 });
} catch (err) {
expect(err.message).toMatch(/internal server error/i);
}
});
});
describe('/v1/languages', () => {
const basePath = `/languages?key=${key}`;
it('should return languages', async () => {
const expectedResponse = {
status: 200,
requests: {
used: 1000,
available: 9000,
resets: '2019-10-01 00:00:00',
},
languages: [
{
code: 'ar',
name: 'Arabic',
},
{
code: 'en',
name: 'English',
},
{
code: 'es',
name: 'Spanish, Castilian',
},
{
code: 'hi',
name: 'Hindi',
},
{
code: 'zh',
name: 'Chinese (Simplified)',
},
],
};
mockRequest.get(basePath).reply(200, expectedResponse);
expect(await holidayapi.languages()).toStrictEqual(expectedResponse);
});
it('should raise 4xx errors', async () => {
const expectedResponse = {
status: 429,
error: 'Rate limit exceeded',
};
expect.assertions(1);
mockRequest.get(basePath).reply(429, expectedResponse);
try {
await holidayapi.languages();
} catch (err) {
expect(err.message).toMatch(/rate limit exceeded/i);
}
});
it('should raise 5xx errors', async () => {
expect.assertions(1);
mockRequest.get(basePath).times(2).reply(500);
try {
await holidayapi.languages();
} catch (err) {
expect(err.message).toMatch(/internal server error/i);
}
});
});
});
});

15
tsconfig.json Normal file
View file

@ -0,0 +1,15 @@
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"declaration": true,
"outDir": "./dist",
"strict": true,
"removeComments": true,
"noUnusedLocals": true,
"noUnusedParameters": true
},
"include": [
"src/**/*"
]
}