@streamr/geoip-location 100.2.4

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.
@@ -0,0 +1,55 @@
1
+ import { GeoIpLocator } from '../../src/GeoIpLocator'
2
+ import fs from 'fs'
3
+ import { wait } from '@streamr/utils'
4
+ import { TestServer } from '../helpers/TestServer'
5
+
6
+ describe('GeoIpLocatorNoNetworkAtMonthly', () => {
7
+ let dirCounter = 0
8
+ const dbPath = '/tmp'
9
+ const serverPort = 31990
10
+ const serverUrl = 'http://localhost:' + serverPort + '/'
11
+
12
+ let testServer: TestServer
13
+ let dbDir: string
14
+ let locator: GeoIpLocator
15
+
16
+ const getDbDir = () => {
17
+ dirCounter++
18
+ return dbPath + '/geolite2-no-nw-monthly' + dirCounter
19
+ }
20
+
21
+ beforeAll(async () => {
22
+ testServer = new TestServer()
23
+ await testServer.start(serverPort)
24
+ dbDir = getDbDir()
25
+ locator = new GeoIpLocator(dbDir, 5000, 10000, serverUrl)
26
+ await locator.start()
27
+ }, 120000)
28
+
29
+ afterAll(async () => {
30
+ locator!.stop()
31
+ testServer!.stop()
32
+ fs.unlinkSync(dbDir + '/GeoLite2-City.mmdb')
33
+ fs.rmSync(dbDir!, { recursive: true })
34
+ })
35
+
36
+ it('does not crash if monthly database check fails because of fetch returning garbage', async () => {
37
+ const oldFetch = globalThis.fetch
38
+ const fetchMock = jest
39
+ .spyOn(globalThis, 'fetch')
40
+ .mockImplementation(() => oldFetch('https://streamr.network'))
41
+
42
+ await wait(10000)
43
+
44
+ fetchMock.mockRestore()
45
+
46
+ // suomi.fi
47
+ const location = locator!.lookup('62.241.198.245')
48
+ expect(location).toBeDefined()
49
+
50
+ // Helsinki, Finland
51
+ expect(location!.latitude).toBe(60.1797)
52
+ expect(location!.longitude).toBe(24.9344)
53
+
54
+ }, 60000)
55
+ })
@@ -0,0 +1,29 @@
1
+ import { GeoIpLocator } from '../../src/GeoIpLocator'
2
+
3
+ describe('GeoIpLocator', () => {
4
+ let dirCounter = 0
5
+ const dbPath = '/tmp'
6
+
7
+ const getDbDir = () => {
8
+ dirCounter++
9
+ return dbPath + '/geolite2-no-nw-start' + dirCounter
10
+ }
11
+
12
+ it('start throws if no network connectivity', async () => {
13
+ const dbDir = getDbDir()
14
+
15
+ const fetchMock = jest
16
+ .spyOn(globalThis, 'fetch')
17
+ .mockImplementation(async () => {
18
+ throw new Error('API is down')
19
+ })
20
+
21
+ const locator = new GeoIpLocator(dbDir)
22
+
23
+ await expect(locator.start()).rejects.toThrow()
24
+
25
+ fetchMock.mockRestore()
26
+
27
+ locator.stop()
28
+ })
29
+ })
@@ -0,0 +1,127 @@
1
+ import { GeoIpLocator } from '../../src/GeoIpLocator'
2
+ import fs from 'fs'
3
+ import { wait } from '@streamr/utils'
4
+ import { TestServer } from '../helpers/TestServer'
5
+
6
+ describe('GeoIpLocator', () => {
7
+ let testServer: TestServer
8
+ let dirCounter = 0
9
+ const dbPath = '/tmp'
10
+ const serverPort = 31992
11
+ const serverUrl = 'http://127.0.0.1:' + serverPort + '/'
12
+
13
+ const getDbDir = () => {
14
+ dirCounter++
15
+ return dbPath + '/geolite2-' + dirCounter
16
+ }
17
+
18
+ beforeAll(async () => {
19
+ testServer = new TestServer()
20
+ await testServer.start(serverPort)
21
+ }, 120000)
22
+
23
+ afterAll(async () => {
24
+ testServer!.stop()
25
+ })
26
+
27
+ describe('tests with normal startup and shutdown', () => {
28
+ let dbDir: string
29
+ let locator: GeoIpLocator
30
+
31
+ it('can locate an IP address', async () => {
32
+ dbDir = getDbDir()
33
+ locator = new GeoIpLocator(dbDir, 5000, 5000, serverUrl)
34
+ await locator.start()
35
+
36
+ // suomi.fi
37
+ const location = locator.lookup('62.241.198.245')
38
+
39
+ expect(location).toBeDefined()
40
+
41
+ // Helsinki, Finland
42
+ expect(location!.latitude).toBe(60.1797)
43
+ expect(location!.longitude).toBe(24.9344)
44
+
45
+ locator.stop()
46
+ fs.unlinkSync(dbDir + '/GeoLite2-City.mmdb')
47
+ fs.rmSync(dbDir, { recursive: true })
48
+ })
49
+
50
+ it('returns undefined with invalid IP address', async () => {
51
+ dbDir = getDbDir()
52
+ locator = new GeoIpLocator(dbDir, 5000, 5000, serverUrl)
53
+ await locator.start()
54
+
55
+ expect(locator.lookup('invalid')).toBeUndefined()
56
+ expect(locator.lookup('')).toBeUndefined()
57
+ expect(locator.lookup(undefined as unknown as string)).toBeUndefined()
58
+ expect(locator.lookup(null as unknown as string)).toBeUndefined()
59
+ expect(locator.lookup('127.0.0.1')).toBeUndefined()
60
+
61
+ locator.stop()
62
+ fs.unlinkSync(dbDir + '/GeoLite2-City.mmdb')
63
+ fs.rmSync(dbDir, { recursive: true })
64
+ })
65
+
66
+ it('works also after monthly check', async () => {
67
+ dbDir = getDbDir()
68
+ locator = new GeoIpLocator(dbDir, 5000, 5000, serverUrl)
69
+ await locator.start()
70
+
71
+ await wait(7000)
72
+
73
+ // suomi.fi
74
+ const location = locator.lookup('62.241.198.245')
75
+ expect(location).toBeDefined()
76
+
77
+ // Helsinki, Finland
78
+ expect(location!.latitude).toBe(60.1797)
79
+ expect(location!.longitude).toBe(24.9344)
80
+
81
+ locator.stop()
82
+ fs.unlinkSync(dbDir + '/GeoLite2-City.mmdb')
83
+ fs.rmSync(dbDir, { recursive: true })
84
+ }, 60000)
85
+
86
+ it('works also after monthly check if db gets deleted before the check', async () => {
87
+ dbDir = getDbDir()
88
+ locator = new GeoIpLocator(dbDir, 5000, 5000, serverUrl)
89
+ await locator.start()
90
+
91
+ fs.unlinkSync(dbDir + '/GeoLite2-City.mmdb')
92
+
93
+ await wait(10000)
94
+
95
+ // suomi.fi
96
+ const location = locator.lookup('62.241.198.245')
97
+ expect(location).toBeDefined()
98
+
99
+ // Helsinki, Finland
100
+ expect(location!.latitude).toBe(60.1797)
101
+ expect(location!.longitude).toBe(24.9344)
102
+
103
+ locator.stop()
104
+ fs.unlinkSync(dbDir + '/GeoLite2-City.mmdb')
105
+ fs.rmSync(dbDir, { recursive: true })
106
+ }, 60000)
107
+ })
108
+
109
+ describe('tests with failing startup', () => {
110
+ it('returns undefined if not started', async () => {
111
+ const dbDir = getDbDir()
112
+ const locator = new GeoIpLocator(dbDir)
113
+ const location = locator.lookup('62.241.198.245')
114
+ expect(location).toBeUndefined()
115
+ })
116
+
117
+ it('start() throws if database path does not exist', async () => {
118
+ const locator = new GeoIpLocator('/nonexistent')
119
+ await expect(locator.start()).rejects.toThrow()
120
+ })
121
+
122
+ it('start() throws if database path is not writable', async () => {
123
+ const locator = new GeoIpLocator('/etc')
124
+ await expect(locator.start()).rejects.toThrow()
125
+ })
126
+ })
127
+ })
@@ -0,0 +1,66 @@
1
+ import { downloadGeoIpDatabase } from '../../src/downloadGeoIpDatabase'
2
+ import fs from 'fs'
3
+ import { TestServer } from '../helpers/TestServer'
4
+
5
+ describe('downloadGeoIpDatabase', () => {
6
+ const serverPort = 31993
7
+ const mirrorUrl = 'http://127.0.0.1:' + serverPort + '/'
8
+
9
+ let testServer: TestServer
10
+ const abortController = new AbortController()
11
+ const path = '/tmp/downloadGeoIpDatabaseTest/'
12
+
13
+ beforeAll(async () => {
14
+ testServer = new TestServer()
15
+ await testServer.start(serverPort)
16
+ }, 120000)
17
+
18
+ afterAll(async () => {
19
+ testServer!.stop()
20
+ })
21
+
22
+ beforeEach(() => {
23
+ try {
24
+ fs.rmSync(path, { recursive: true })
25
+ } catch (e) {
26
+ // ignore error when removing the test data
27
+ }
28
+ })
29
+
30
+ it('downloads the database with correct file permissions', async () => {
31
+ const reader = await downloadGeoIpDatabase(path, false, abortController.signal, mirrorUrl)
32
+
33
+ expect(fs.existsSync(path)).toBe(true)
34
+ expect(fs.existsSync(path + '/GeoLite2-City.mmdb')).toBe(true)
35
+
36
+ // https://www.martin-brennan.com/nodejs-file-permissions-fstat/
37
+ const permissions = fs.statSync(path + '/GeoLite2-City.mmdb').mode & 0o777
38
+
39
+ // on windows the permissions might be 0o666
40
+ expect(permissions === 0o600 || permissions === 0o666).toBe(true)
41
+ expect(reader).toBeDefined()
42
+ }, 60000)
43
+
44
+ it('throws if the path is not writable', async () => {
45
+ const path = '/etc/downloadGeoIpDatabaseTest/'
46
+ await expect(downloadGeoIpDatabase(path, false, abortController.signal, mirrorUrl)).rejects.toThrow()
47
+ }, 60000)
48
+
49
+ it('throws if the path does not exist', async () => {
50
+ const path = '/nonexistent/downloadGeoIpDatabaseTest/'
51
+ await expect(downloadGeoIpDatabase(path, false, abortController.signal, mirrorUrl)).rejects.toThrow()
52
+ }, 60000)
53
+
54
+ it('does not download the database if it is already up to date', async () => {
55
+ const path = '/tmp/downloadGeoIpDatabaseTest/'
56
+
57
+ const newReader = await downloadGeoIpDatabase(path, false, abortController.signal, mirrorUrl)
58
+ expect(newReader).toBeDefined()
59
+
60
+ const newReader2 = await downloadGeoIpDatabase(path, false, abortController.signal, mirrorUrl)
61
+ expect(newReader2).toBeUndefined()
62
+
63
+ const newReader3 = await downloadGeoIpDatabase(path, true, abortController.signal, mirrorUrl)
64
+ expect(newReader3).toBeDefined()
65
+ }, 60000)
66
+ })
@@ -0,0 +1,92 @@
1
+ import { waitForEvent3 } from '@streamr/utils'
2
+ import { extractFileFromTarStream } from '../../src/tarHelper'
3
+ import { TestServer, TestServerEvents } from '../helpers/TestServer'
4
+
5
+ describe('tarHelper', () => {
6
+ const serverUrl = 'http://127.0.0.1:'
7
+ const dbFileName = 'GeoLite2-City.mmdb'
8
+ const tarFileName = 'GeoLite2-City.tar.gz'
9
+ const hashFileName = 'GeoLite2-City.mmdb.sha384'
10
+
11
+ let testServer: TestServer
12
+
13
+ afterEach(async () => {
14
+ await testServer!.stop()
15
+ })
16
+
17
+ describe('testsWithNormalServer', () => {
18
+ const serverPort = 3197
19
+
20
+ beforeEach(async () => {
21
+ testServer = new TestServer()
22
+ await testServer.start(serverPort)
23
+ })
24
+
25
+ it('happy path', async () => {
26
+ const url = serverUrl + serverPort + '/' + tarFileName
27
+ const result = await fetch(url, { keepalive: false })
28
+
29
+ await extractFileFromTarStream(dbFileName, result.body!, '/tmp')
30
+
31
+ })
32
+
33
+ it('throws asynchonously if the stream contains garbage', async () => {
34
+ const url = serverUrl + serverPort + '/' + hashFileName
35
+ const result = await fetch(url)
36
+
37
+ await expect(extractFileFromTarStream(dbFileName, result.body!, '/tmp'))
38
+ .rejects
39
+ .toThrow('TAR_BAD_ARCHIVE: Unrecognized archive format')
40
+
41
+ })
42
+
43
+ it('throws asynchonously if the stream does not contain the desired file', async () => {
44
+ const url = serverUrl + serverPort + '/' + tarFileName
45
+ const result = await fetch(url)
46
+
47
+ await expect(extractFileFromTarStream('nonexisting-filename', result.body!, '/tmp'))
48
+ .rejects
49
+ .toThrow('File not found in tarball: nonexisting-filename')
50
+
51
+ })
52
+ })
53
+
54
+ describe('testsWithThrottledServer', () => {
55
+ const serverPort = 3198
56
+
57
+ beforeEach(async () => {
58
+ testServer = new TestServer()
59
+ await testServer.start(serverPort, 1)
60
+ })
61
+
62
+ it('throws asynchonously if the stream gets aborted', async () => {
63
+ const abortController = new AbortController()
64
+
65
+ setTimeout(() => {
66
+ abortController.abort()
67
+ }, 5000)
68
+
69
+ const url = serverUrl + serverPort + '/' + tarFileName
70
+ const result = await fetch(url, { signal: abortController.signal })
71
+
72
+ await expect(extractFileFromTarStream(dbFileName, result.body!, '/tmp'))
73
+ .rejects
74
+ .toThrow('AbortError: This operation was aborted')
75
+
76
+ }, 15 * 1000)
77
+
78
+ it('throws asynchonously if server gets shut down', async () => {
79
+ const closedPromise = waitForEvent3<TestServerEvents>(testServer!, 'closed', 10000)
80
+ setTimeout(async () => {
81
+ await testServer!.stop()
82
+ }, 5000)
83
+
84
+ const url = serverUrl + serverPort + '/' + tarFileName
85
+ const result = await fetch(url)
86
+ await expect(extractFileFromTarStream(dbFileName, result.body!, '/tmp'))
87
+ .rejects
88
+ .toThrow('Error extracting tarball')
89
+ await closedPromise
90
+ }, 15 * 1000)
91
+ })
92
+ })
@@ -0,0 +1,12 @@
1
+ {
2
+ "extends": "../../tsconfig.browser.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "rootDirs": ["src", "test"],
6
+ "noImplicitOverride": false
7
+ },
8
+ "include": [
9
+ "src/**/*",
10
+ "test/**/*"
11
+ ]
12
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "extends": "../../tsconfig.jest.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "noImplicitOverride": false
6
+ },
7
+ "include": [
8
+ "src/**/*",
9
+ "test/**/*"
10
+ ],
11
+ "references": [
12
+ { "path": "../utils/tsconfig.node.json" }
13
+ ]
14
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,3 @@
1
+ {
2
+ "extends": "./tsconfig.jest.json"
3
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "extends": "../../tsconfig.node.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "noImplicitOverride": false
6
+ },
7
+ "include": [
8
+ "src/**/*"
9
+ ],
10
+ "references": [
11
+ { "path": "../utils/tsconfig.node.json" },
12
+ ]
13
+ }