@k03mad/ip2geo 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +12 -0
- package/.github/dependabot.yml +10 -0
- package/.github/workflows/lint.yml +32 -0
- package/.github/workflows/test.yml +32 -0
- package/.husky/pre-commit +4 -0
- package/.nvmrc +1 -0
- package/.vscode/settings.json +3 -0
- package/LICENSE +21 -0
- package/README.md +38 -0
- package/app/index.js +1 -0
- package/app/lib/ip2geo.js +164 -0
- package/eslint.config.js +1 -0
- package/package.json +37 -0
- package/tests/opts-assigned-subfolder.js +44 -0
- package/tests/opts-assigned.js +47 -0
- package/tests/opts-default.js +67 -0
- package/tests/shared/fs.js +34 -0
package/.editorconfig
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
name: Lint
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- master
|
|
7
|
+
pull_request:
|
|
8
|
+
branches:
|
|
9
|
+
- master
|
|
10
|
+
schedule:
|
|
11
|
+
- cron: '00 15 * * 6'
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
lint:
|
|
15
|
+
name: Lint
|
|
16
|
+
permissions:
|
|
17
|
+
contents: read
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
steps:
|
|
20
|
+
- name: Checkout
|
|
21
|
+
uses: actions/checkout@v4
|
|
22
|
+
|
|
23
|
+
- name: Install NodeJS
|
|
24
|
+
uses: actions/setup-node@v4
|
|
25
|
+
with:
|
|
26
|
+
node-version-file: '.nvmrc'
|
|
27
|
+
|
|
28
|
+
- name: Run setup
|
|
29
|
+
run: npm run setup
|
|
30
|
+
|
|
31
|
+
- name: Run lint
|
|
32
|
+
run: npm run lint
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
name: Test
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- master
|
|
7
|
+
pull_request:
|
|
8
|
+
branches:
|
|
9
|
+
- master
|
|
10
|
+
schedule:
|
|
11
|
+
- cron: '00 15 * * 6'
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
test:
|
|
15
|
+
name: Test
|
|
16
|
+
permissions:
|
|
17
|
+
contents: read
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
steps:
|
|
20
|
+
- name: Checkout
|
|
21
|
+
uses: actions/checkout@v4
|
|
22
|
+
|
|
23
|
+
- name: Install NodeJS
|
|
24
|
+
uses: actions/setup-node@v4
|
|
25
|
+
with:
|
|
26
|
+
node-version-file: '.nvmrc'
|
|
27
|
+
|
|
28
|
+
- name: Run setup
|
|
29
|
+
run: npm run setup
|
|
30
|
+
|
|
31
|
+
- name: Run test
|
|
32
|
+
run: npm run test
|
package/.nvmrc
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
20
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Kirill Molchanov
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# GeoIP lookup
|
|
2
|
+
|
|
3
|
+
— Using [ipwhois.io](https://ipwhois.io/documentation) \
|
|
4
|
+
— Runtime cache \
|
|
5
|
+
— Filesystem infinity cache
|
|
6
|
+
|
|
7
|
+
## API
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm i @k03mad/ip2geo --save-exact
|
|
11
|
+
echo "/geoip" >> .gitignore
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
```js
|
|
15
|
+
import {ip2geo} from '@k03mad/ip2geo';
|
|
16
|
+
|
|
17
|
+
const {
|
|
18
|
+
ip,
|
|
19
|
+
emoji,
|
|
20
|
+
country,
|
|
21
|
+
countryA2,
|
|
22
|
+
city,
|
|
23
|
+
isp,
|
|
24
|
+
} = await ip2geo('1.1.1.1', {
|
|
25
|
+
// defaults
|
|
26
|
+
cacheDir: 'geoip',
|
|
27
|
+
cacheFileName: 'ip.log',
|
|
28
|
+
cacheFileSeparator: ';;',
|
|
29
|
+
cacheFileNewline: '\n',
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// ip: "1.1.1.1"
|
|
33
|
+
// emoji: "🇺🇸"
|
|
34
|
+
// country: "United States"
|
|
35
|
+
// countryA2: "US"
|
|
36
|
+
// city: "Washington"
|
|
37
|
+
// isp: "Cloudflare, Inc."
|
|
38
|
+
```
|
package/app/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {default as ip2geo} from './lib/ip2geo.js';
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import {request} from '@k03mad/request';
|
|
5
|
+
import _debug from 'debug';
|
|
6
|
+
|
|
7
|
+
const debug = _debug('mad:geoip');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {object} GeoIpOutput
|
|
11
|
+
* @property {string} [ip]
|
|
12
|
+
* @property {string} [emoji]
|
|
13
|
+
* @property {string} [country]
|
|
14
|
+
* @property {string} [countryA2]
|
|
15
|
+
* @property {string} [city]
|
|
16
|
+
* @property {string} [isp]
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const API = 'https://ipwho.is/';
|
|
20
|
+
|
|
21
|
+
const CACHE_FILE_DIR = 'geoip';
|
|
22
|
+
const CACHE_FILE_NAME = 'ip.log';
|
|
23
|
+
const CACHE_FILE_SEPARATOR = ';;';
|
|
24
|
+
const CACHE_FILE_NEWLINE = '\n';
|
|
25
|
+
|
|
26
|
+
const cacheMap = new Map();
|
|
27
|
+
|
|
28
|
+
const outputKeys = [
|
|
29
|
+
'ip',
|
|
30
|
+
'emoji',
|
|
31
|
+
'country',
|
|
32
|
+
'countryA2',
|
|
33
|
+
'city',
|
|
34
|
+
'isp',
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @param {Array<string>} dataArr
|
|
39
|
+
* @returns {GeoIpOutput}
|
|
40
|
+
*/
|
|
41
|
+
const collectOutputData = dataArr => {
|
|
42
|
+
const outputData = {};
|
|
43
|
+
|
|
44
|
+
outputKeys.forEach((key, i) => {
|
|
45
|
+
outputData[key] = dataArr[i];
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
return outputData;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @param {string} ip
|
|
53
|
+
* @param {string} cacheDir
|
|
54
|
+
* @param {string} cacheFileName
|
|
55
|
+
* @returns {string}
|
|
56
|
+
*/
|
|
57
|
+
const getCacheFileFullPath = (ip, cacheDir, cacheFileName) => {
|
|
58
|
+
const [firstOctet] = ip.split('.');
|
|
59
|
+
return path.join(cacheDir, `${firstOctet}_${cacheFileName}`);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @param {string} ip
|
|
64
|
+
* @param {string} cacheDir
|
|
65
|
+
* @param {string} cacheFileName
|
|
66
|
+
* @returns {Promise<string>}
|
|
67
|
+
*/
|
|
68
|
+
const readFromFsCache = async (ip, cacheDir, cacheFileName) => {
|
|
69
|
+
try {
|
|
70
|
+
await fs.mkdir(cacheDir, {recursive: true});
|
|
71
|
+
return await fs.readFile(getCacheFileFullPath(ip, cacheDir, cacheFileName), {encoding: 'utf8'});
|
|
72
|
+
} catch (err) {
|
|
73
|
+
if (err.code === 'ENOENT') {
|
|
74
|
+
await fs.appendFile(getCacheFileFullPath(ip, cacheDir, cacheFileName), '');
|
|
75
|
+
} else {
|
|
76
|
+
throw err;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @param {string} ip
|
|
83
|
+
* @param {Array[string]} data
|
|
84
|
+
* @param {string} cacheDir
|
|
85
|
+
* @param {string} cacheFileName
|
|
86
|
+
* @param {string} cacheFileSeparator
|
|
87
|
+
* @param {string} cacheFileNewline
|
|
88
|
+
* @returns {Promise<void>}
|
|
89
|
+
*/
|
|
90
|
+
const writeToFsCache = async (ip, data, cacheDir, cacheFileName, cacheFileSeparator, cacheFileNewline) => {
|
|
91
|
+
await fs.mkdir(cacheDir, {recursive: true});
|
|
92
|
+
|
|
93
|
+
await fs.appendFile(
|
|
94
|
+
getCacheFileFullPath(ip, cacheDir, cacheFileName),
|
|
95
|
+
data.join(cacheFileSeparator) + cacheFileNewline,
|
|
96
|
+
);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* @param {string} [ip]
|
|
101
|
+
* @param {object} [opts]
|
|
102
|
+
* @param {string} [opts.cacheDir]
|
|
103
|
+
* @param {string} [opts.cacheFileName]
|
|
104
|
+
* @param {string} [opts.cacheFileSeparator]
|
|
105
|
+
* @param {string} [opts.cacheFileNewline]
|
|
106
|
+
* @returns {Promise<GeoIpOutput>}
|
|
107
|
+
*/
|
|
108
|
+
export default async (ip = '', {
|
|
109
|
+
cacheDir = CACHE_FILE_DIR,
|
|
110
|
+
cacheFileName = CACHE_FILE_NAME,
|
|
111
|
+
cacheFileSeparator = CACHE_FILE_SEPARATOR,
|
|
112
|
+
cacheFileNewline = CACHE_FILE_NEWLINE,
|
|
113
|
+
} = {}) => {
|
|
114
|
+
if (ip) {
|
|
115
|
+
const ipData = cacheMap.get(ip);
|
|
116
|
+
|
|
117
|
+
if (ipData) {
|
|
118
|
+
debug('get from map cache: %o', ipData);
|
|
119
|
+
return ipData;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const fsCache = await readFromFsCache(ip, cacheDir, cacheFileName);
|
|
123
|
+
|
|
124
|
+
if (fsCache) {
|
|
125
|
+
const data = fsCache.split(cacheFileNewline);
|
|
126
|
+
|
|
127
|
+
for (const elem of data) {
|
|
128
|
+
const fileData = elem.split(cacheFileSeparator);
|
|
129
|
+
|
|
130
|
+
if (ip === fileData[0]) {
|
|
131
|
+
const outputData = collectOutputData(fileData);
|
|
132
|
+
debug('get from fs cache: %o', outputData);
|
|
133
|
+
|
|
134
|
+
cacheMap.set(ip, outputData);
|
|
135
|
+
return outputData;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const {body} = await request(API + ip);
|
|
142
|
+
|
|
143
|
+
const usedData = [
|
|
144
|
+
body.ip,
|
|
145
|
+
body?.flag?.emoji,
|
|
146
|
+
body?.country,
|
|
147
|
+
body?.country_code,
|
|
148
|
+
body?.city,
|
|
149
|
+
body?.connection?.isp,
|
|
150
|
+
];
|
|
151
|
+
|
|
152
|
+
const outputData = collectOutputData(usedData);
|
|
153
|
+
|
|
154
|
+
cacheMap.set(body.ip, outputData);
|
|
155
|
+
debug('set to map cache: %o', outputData);
|
|
156
|
+
|
|
157
|
+
await writeToFsCache(
|
|
158
|
+
body.ip, usedData,
|
|
159
|
+
cacheDir, cacheFileName, cacheFileSeparator, cacheFileNewline,
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
debug('set to fs cache: %o', outputData);
|
|
163
|
+
return outputData;
|
|
164
|
+
};
|
package/eslint.config.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {default} from '@k03mad/eslint-config';
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@k03mad/ip2geo",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "GeoIP library",
|
|
5
|
+
"maintainers": [
|
|
6
|
+
"Kirill Molchanov <k03.mad@gmail.com"
|
|
7
|
+
],
|
|
8
|
+
"main": "app/index.js",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/k03mad/ip2geo.git"
|
|
12
|
+
},
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"type": "module",
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=20"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@k03mad/request": "5.4.1",
|
|
20
|
+
"debug": "4.3.4"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@k03mad/eslint-config": "19.2.0",
|
|
24
|
+
"eslint": "8.56.0",
|
|
25
|
+
"husky": "8.0.3"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"lint": "npm run lint:eslint",
|
|
29
|
+
"lint:eslint": "eslint ./ --cache",
|
|
30
|
+
"test": "node --test tests/*",
|
|
31
|
+
"clean": "npm run clean:modules && npm run clean:eslint:cache",
|
|
32
|
+
"clean:modules": "rm -rf ./node_modules || true",
|
|
33
|
+
"clean:eslint:cache": "rm -rf .eslintcache || true",
|
|
34
|
+
"setup": "npm run clean && npm i",
|
|
35
|
+
"prepare": "husky install || true"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import {describe, it} from 'node:test';
|
|
3
|
+
|
|
4
|
+
import {ip2geo} from '../app/index.js';
|
|
5
|
+
|
|
6
|
+
import {checkCacheFile, removeCacheFolder} from './shared/fs.js';
|
|
7
|
+
|
|
8
|
+
describe('opts-assigned', () => {
|
|
9
|
+
const CACHE_FILE_DIR = 'geoip-subfolder/hello_there';
|
|
10
|
+
const CACHE_FILE_NAME = 'ip.log';
|
|
11
|
+
const CACHE_FILE_SEPARATOR = ';;';
|
|
12
|
+
const CACHE_FILE_NEWLINE = '\n';
|
|
13
|
+
|
|
14
|
+
const REQUEST_IP = '9.9.9.9';
|
|
15
|
+
|
|
16
|
+
const cacheFile = `${REQUEST_IP.split('.')[0]}_${CACHE_FILE_NAME}`;
|
|
17
|
+
|
|
18
|
+
const response = {
|
|
19
|
+
ip: REQUEST_IP,
|
|
20
|
+
emoji: '🇨ðŸ‡',
|
|
21
|
+
country: 'Switzerland',
|
|
22
|
+
countryA2: 'CH',
|
|
23
|
+
city: 'Zürich',
|
|
24
|
+
isp: 'Quad9',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
removeCacheFolder(CACHE_FILE_DIR);
|
|
28
|
+
|
|
29
|
+
it(`should return correct response for IP: "${REQUEST_IP}"`, async () => {
|
|
30
|
+
const data = await ip2geo(REQUEST_IP, {
|
|
31
|
+
cacheDir: CACHE_FILE_DIR,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
assert.deepEqual(data, response);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
checkCacheFile(
|
|
38
|
+
CACHE_FILE_DIR,
|
|
39
|
+
cacheFile,
|
|
40
|
+
CACHE_FILE_SEPARATOR,
|
|
41
|
+
CACHE_FILE_NEWLINE,
|
|
42
|
+
response,
|
|
43
|
+
);
|
|
44
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import {describe, it} from 'node:test';
|
|
3
|
+
|
|
4
|
+
import {ip2geo} from '../app/index.js';
|
|
5
|
+
|
|
6
|
+
import {checkCacheFile, removeCacheFolder} from './shared/fs.js';
|
|
7
|
+
|
|
8
|
+
describe('opts-assigned', () => {
|
|
9
|
+
const CACHE_FILE_DIR = 'geoip-cache-opts';
|
|
10
|
+
const CACHE_FILE_NAME = 'ips.md';
|
|
11
|
+
const CACHE_FILE_SEPARATOR = '-_-';
|
|
12
|
+
const CACHE_FILE_NEWLINE = '%%%';
|
|
13
|
+
|
|
14
|
+
const REQUEST_IP = '8.8.8.8';
|
|
15
|
+
|
|
16
|
+
const cacheFile = `${REQUEST_IP.split('.')[0]}_${CACHE_FILE_NAME}`;
|
|
17
|
+
|
|
18
|
+
const response = {
|
|
19
|
+
ip: REQUEST_IP,
|
|
20
|
+
emoji: '🇺🇸',
|
|
21
|
+
country: 'United States',
|
|
22
|
+
countryA2: 'US',
|
|
23
|
+
city: 'Mountain View',
|
|
24
|
+
isp: 'Google LLC',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
removeCacheFolder(CACHE_FILE_DIR);
|
|
28
|
+
|
|
29
|
+
it(`should return correct response for IP: "${REQUEST_IP}"`, async () => {
|
|
30
|
+
const data = await ip2geo(REQUEST_IP, {
|
|
31
|
+
cacheDir: CACHE_FILE_DIR,
|
|
32
|
+
cacheFileName: CACHE_FILE_NAME,
|
|
33
|
+
cacheFileSeparator: CACHE_FILE_SEPARATOR,
|
|
34
|
+
cacheFileNewline: CACHE_FILE_NEWLINE,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
assert.deepEqual(data, response);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
checkCacheFile(
|
|
41
|
+
CACHE_FILE_DIR,
|
|
42
|
+
cacheFile,
|
|
43
|
+
CACHE_FILE_SEPARATOR,
|
|
44
|
+
CACHE_FILE_NEWLINE,
|
|
45
|
+
response,
|
|
46
|
+
);
|
|
47
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import {describe, it} from 'node:test';
|
|
3
|
+
|
|
4
|
+
import {ip2geo} from '../app/index.js';
|
|
5
|
+
|
|
6
|
+
import {checkCacheFile, removeCacheFolder} from './shared/fs.js';
|
|
7
|
+
|
|
8
|
+
describe('opts-default', () => {
|
|
9
|
+
const CACHE_FILE_DIR = 'geoip';
|
|
10
|
+
const CACHE_FILE_NAME = 'ip.log';
|
|
11
|
+
const CACHE_FILE_SEPARATOR = ';;';
|
|
12
|
+
const CACHE_FILE_NEWLINE = '\n';
|
|
13
|
+
|
|
14
|
+
const REQUEST_IP = '1.1.1.1';
|
|
15
|
+
|
|
16
|
+
const cacheFile = `${REQUEST_IP.split('.')[0]}_${CACHE_FILE_NAME}`;
|
|
17
|
+
|
|
18
|
+
const response = {
|
|
19
|
+
ip: REQUEST_IP,
|
|
20
|
+
emoji: '🇺🇸',
|
|
21
|
+
country: 'United States',
|
|
22
|
+
countryA2: 'US',
|
|
23
|
+
city: 'Washington',
|
|
24
|
+
isp: 'Cloudflare, Inc.',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const outputKeys = [
|
|
28
|
+
'ip',
|
|
29
|
+
'emoji',
|
|
30
|
+
'country',
|
|
31
|
+
'countryA2',
|
|
32
|
+
'city',
|
|
33
|
+
'isp',
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
removeCacheFolder(CACHE_FILE_DIR);
|
|
37
|
+
|
|
38
|
+
describe('with ip arg', () => {
|
|
39
|
+
it(`should return correct response for IP: "${REQUEST_IP}"`, async () => {
|
|
40
|
+
const data = await ip2geo(REQUEST_IP);
|
|
41
|
+
|
|
42
|
+
assert.deepEqual(data, response);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
checkCacheFile(
|
|
46
|
+
CACHE_FILE_DIR,
|
|
47
|
+
cacheFile,
|
|
48
|
+
CACHE_FILE_SEPARATOR,
|
|
49
|
+
CACHE_FILE_NEWLINE,
|
|
50
|
+
response,
|
|
51
|
+
);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe('without ip arg', () => {
|
|
55
|
+
let data;
|
|
56
|
+
|
|
57
|
+
it('should request geoip without ip arg', async () => {
|
|
58
|
+
data = await ip2geo();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
outputKeys.forEach(key => {
|
|
62
|
+
it(`should have "${key}" in request response`, () => {
|
|
63
|
+
assert.ok(data[key]);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import {it} from 'node:test';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @param {string} cacheDir
|
|
8
|
+
*/
|
|
9
|
+
export const removeCacheFolder = cacheDir => {
|
|
10
|
+
it('should remove fs cache dir if exist', async () => {
|
|
11
|
+
try {
|
|
12
|
+
await fs.rm(cacheDir, {recursive: true, force: true});
|
|
13
|
+
} catch (err) {
|
|
14
|
+
if (err.code !== 'ENOENT') {
|
|
15
|
+
throw err;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {string} cacheDir
|
|
23
|
+
* @param {string} cacheFileName
|
|
24
|
+
* @param {string} cacheFileSeparator
|
|
25
|
+
* @param {string} cacheFileNewline
|
|
26
|
+
* @param {object} response
|
|
27
|
+
*/
|
|
28
|
+
export const checkCacheFile = (cacheDir, cacheFileName, cacheFileSeparator, cacheFileNewline, response) => {
|
|
29
|
+
it('should have cache file', async () => {
|
|
30
|
+
const data = await fs.readFile(path.join(cacheDir, cacheFileName), {encoding: 'utf8'});
|
|
31
|
+
|
|
32
|
+
assert.equal(data, Object.values(response).join(cacheFileSeparator) + cacheFileNewline);
|
|
33
|
+
});
|
|
34
|
+
};
|