diff --git a/src/client.test.ts b/src/client.test.ts index 34b373c2..ce13bcfd 100644 --- a/src/client.test.ts +++ b/src/client.test.ts @@ -2,26 +2,23 @@ import { jest } from '@jest/globals'; import nock from 'nock'; import { Blob } from 'node-fetch'; import { Client } from './client'; -import type { Host } from './config'; +import { Host } from './config'; import { Config } from './config'; import { ErrorNotification } from './notify'; describe('Client', () => { - const host: Host = { - baseUrl: 'http://10.0.0.2', - password: 'mypassword' - }; + const host = new Host('http://10.0.0.2', 'mypassword'); const createClient = async () => { - nock(host.baseUrl).get('/admin/index.php?login').reply(200); - nock(host.baseUrl) + nock(host.fullUrl).get('/admin/index.php?login').reply(200); + nock(host.fullUrl) .post('/admin/index.php?login') .reply( 200, '
abcdefgijklmnopqrstuvwxyzabcdefgijklmnopqrst
' ); - return { teleporter: nock(host.baseUrl), client: await Client.create(host) }; + return { teleporter: nock(host.fullUrl), client: await Client.create(host) }; }; beforeEach(() => { @@ -30,8 +27,8 @@ describe('Client', () => { describe('create', () => { test('should throw error if status code is not ok', async () => { - const initialRequest = nock(host.baseUrl).get('/admin/index.php?login').reply(200); - const loginRequest = nock(host.baseUrl).post('/admin/index.php?login').reply(500); + const initialRequest = nock(host.fullUrl).get('/admin/index.php?login').reply(200); + const loginRequest = nock(host.fullUrl).post('/admin/index.php?login').reply(500); const expectError = expect(Client.create(host)).rejects; @@ -50,8 +47,8 @@ describe('Client', () => { }); test('should throw error if no token is present', async () => { - const initialRequest = nock(host.baseUrl).get('/admin/index.php?login').reply(200); - const loginRequest = nock(host.baseUrl).post('/admin/index.php?login').reply(200); + const initialRequest = nock(host.fullUrl).get('/admin/index.php?login').reply(200); + const loginRequest = nock(host.fullUrl).post('/admin/index.php?login').reply(200); const expectError = expect(Client.create(host)).rejects; @@ -69,8 +66,8 @@ describe('Client', () => { }); test('should throw error if token is in incorrect format', async () => { - const initialRequest = nock(host.baseUrl).get('/admin/index.php?login').reply(200); - const loginRequest = nock(host.baseUrl) + const initialRequest = nock(host.fullUrl).get('/admin/index.php?login').reply(200); + const loginRequest = nock(host.fullUrl) .post('/admin/index.php?login') .reply(200, '
abcdef
'); @@ -90,8 +87,8 @@ describe('Client', () => { }); test('should return client', async () => { - const initialRequest = nock(host.baseUrl).get('/admin/index.php?login').reply(200); - const loginRequest = nock(host.baseUrl) + const initialRequest = nock(host.fullUrl).get('/admin/index.php?login').reply(200); + const loginRequest = nock(host.fullUrl) .post('/admin/index.php?login') .reply( 200, @@ -237,18 +234,18 @@ describe('Client', () => { .reply( 200, 'Processed adlist (14 entries)
\n' + - 'Processed adlist group assignments (13 entries)
\n' + - 'Processed blacklist (exact) (0 entries)
\n' + - 'Processed blacklist (regex) (3 entries)
\n' + - 'Processed client (8 entries)
\n' + - 'Processed client group assignments (16 entries)
\n' + - 'Processed local DNS records (41 entries)
\n' + - 'Processed domain_audit (0 entries)
\n' + - 'Processed black-/whitelist group assignments (10 entries)
\n' + - 'Processed group (3 entries)
\n' + - 'Processed whitelist (exact) (4 entries)
\n' + - 'Processed whitelist (regex) (0 entries)
\n' + - 'OK' + 'Processed adlist group assignments (13 entries)
\n' + + 'Processed blacklist (exact) (0 entries)
\n' + + 'Processed blacklist (regex) (3 entries)
\n' + + 'Processed client (8 entries)
\n' + + 'Processed client group assignments (16 entries)
\n' + + 'Processed local DNS records (41 entries)
\n' + + 'Processed domain_audit (0 entries)
\n' + + 'Processed black-/whitelist group assignments (10 entries)
\n' + + 'Processed group (3 entries)
\n' + + 'Processed whitelist (exact) (4 entries)
\n' + + 'Processed whitelist (regex) (0 entries)
\n' + + 'OK' ); teleporter .get('/admin/scripts/pi-hole/php/gravity.sh.php', undefined) @@ -276,18 +273,18 @@ describe('Client', () => { .reply( 200, 'Processed adlist (14 entries)
\n' + - 'Processed adlist group assignments (13 entries)
\n' + - 'Processed blacklist (exact) (0 entries)
\n' + - 'Processed blacklist (regex) (3 entries)
\n' + - 'Processed client (8 entries)
\n' + - 'Processed client group assignments (16 entries)
\n' + - 'Processed local DNS records (41 entries)
\n' + - 'Processed domain_audit (0 entries)
\n' + - 'Processed black-/whitelist group assignments (10 entries)
\n' + - 'Processed group (3 entries)
\n' + - 'Processed whitelist (exact) (4 entries)
\n' + - 'Processed whitelist (regex) (0 entries)
\n' + - 'OK' + 'Processed adlist group assignments (13 entries)
\n' + + 'Processed blacklist (exact) (0 entries)
\n' + + 'Processed blacklist (regex) (3 entries)
\n' + + 'Processed client (8 entries)
\n' + + 'Processed client group assignments (16 entries)
\n' + + 'Processed local DNS records (41 entries)
\n' + + 'Processed domain_audit (0 entries)
\n' + + 'Processed black-/whitelist group assignments (10 entries)
\n' + + 'Processed group (3 entries)
\n' + + 'Processed whitelist (exact) (4 entries)
\n' + + 'Processed whitelist (regex) (0 entries)
\n' + + 'OK' ); teleporter .get('/admin/scripts/pi-hole/php/gravity.sh.php', undefined) @@ -336,18 +333,18 @@ describe('Client', () => { .reply( 200, 'Processed adlist (14 entries)
\n' + - 'Processed adlist group assignments (13 entries)
\n' + - 'Processed blacklist (exact) (0 entries)
\n' + - 'Processed blacklist (regex) (3 entries)
\n' + - 'Processed client (8 entries)
\n' + - 'Processed client group assignments (16 entries)
\n' + - 'Processed local DNS records (41 entries)
\n' + - 'Processed domain_audit (0 entries)
\n' + - 'Processed black-/whitelist group assignments (10 entries)
\n' + - 'Processed group (3 entries)
\n' + - 'Processed whitelist (exact) (4 entries)
\n' + - 'Processed whitelist (regex) (0 entries)
\n' + - 'OK' + 'Processed adlist group assignments (13 entries)
\n' + + 'Processed blacklist (exact) (0 entries)
\n' + + 'Processed blacklist (regex) (3 entries)
\n' + + 'Processed client (8 entries)
\n' + + 'Processed client group assignments (16 entries)
\n' + + 'Processed local DNS records (41 entries)
\n' + + 'Processed domain_audit (0 entries)
\n' + + 'Processed black-/whitelist group assignments (10 entries)
\n' + + 'Processed group (3 entries)
\n' + + 'Processed whitelist (exact) (4 entries)
\n' + + 'Processed whitelist (regex) (0 entries)
\n' + + 'OK' ); const result = await client.uploadBackup(backup); diff --git a/src/client.ts b/src/client.ts index 5d049094..5448fd95 100644 --- a/src/client.ts +++ b/src/client.ts @@ -18,14 +18,14 @@ export class Client { private fetch: NodeFetchCookie, private host: Host, private token: string - ) {} + ) { } public static async create(host: Host): Promise { - Log.info(chalk.yellow(`➡️ Signing in to ${host.baseUrl}...`)); + Log.info(chalk.yellow(`➡️ Signing in to ${host.fullUrl}...`)); const fetch = fetchCookie(nodeFetch); - await fetch(`${host.baseUrl}/admin/index.php?login`, { method: 'GET' }); - const response = await fetch(`${host.baseUrl}/admin/index.php?login`, { + await fetch(`${host.fullUrl}/admin/index.php?login`, { method: 'GET' }); + const response = await fetch(`${host.fullUrl}/admin/index.php?login`, { headers: { 'content-type': 'application/x-www-form-urlencoded' }, @@ -34,9 +34,9 @@ export class Client { }); if (response.status !== 200) throw new ErrorNotification({ - message: `There was an error logging in to "${host.baseUrl}" - are you able to log in with the configured password?`, + message: `There was an error logging in to "${host.fullUrl}" - are you able to log in with the configured password?`, verbose: { - host: host.baseUrl, + host: host.fullUrl, status: response.status, responseBody: await response.text() } @@ -44,7 +44,7 @@ export class Client { const token = this.parseResponseForToken(host, await response.text()); - Log.info(chalk.green(`✔️ Successfully signed in to ${host.baseUrl}!`)); + Log.info(chalk.green(`✔️ Successfully signed in to ${host.fullUrl}!`)); return new this(fetch, host, token); } @@ -53,9 +53,9 @@ export class Client { const tokenDiv = root.querySelector('#token'); if (!tokenDiv) throw new ErrorNotification({ - message: `No token could be found while logging in to "${host.baseUrl}" - are you able to log in with the configured password?`, + message: `No token could be found while logging in to "${host.fullUrl}" - are you able to log in with the configured password?`, verbose: { - host: host.baseUrl, + host: host.fullUrl, innerHtml: root.innerHTML } }); @@ -63,9 +63,9 @@ export class Client { const token = tokenDiv.innerText; if (token.length != 44) throw new ErrorNotification({ - message: `A token was found but could not be validated while logging in to "${host.baseUrl}" - are you able to log in with the configured password?`, + message: `A token was found but could not be validated while logging in to "${host.fullUrl}" - are you able to log in with the configured password?`, verbose: { - host: host.baseUrl, + host: host.fullUrl, token: token } }); @@ -74,11 +74,11 @@ export class Client { } public async downloadBackup(): Promise { - Log.info(chalk.yellow(`➡️ Downloading backup from ${this.host.baseUrl}...`)); + Log.info(chalk.yellow(`➡️ Downloading backup from ${this.host.fullUrl}...`)); const form = this.generateForm(); const response = await this.fetch( - `${this.host.baseUrl}/admin/scripts/pi-hole/php/teleporter.php`, + `${this.host.fullUrl}/admin/scripts/pi-hole/php/teleporter.php`, { body: form, method: 'POST' @@ -89,9 +89,9 @@ export class Client { response.headers.get('content-type') !== 'application/gzip' ) throw new ErrorNotification({ - message: `Failed to download backup from "${this.host.baseUrl}".`, + message: `Failed to download backup from "${this.host.fullUrl}".`, verbose: { - host: this.host.baseUrl, + host: this.host.fullUrl, status: response.status, responseBody: await response.text() } @@ -99,19 +99,19 @@ export class Client { const data = await response.arrayBuffer(); - Log.info(chalk.green(`✔️ Backup from ${this.host.baseUrl} completed!`)); + Log.info(chalk.green(`✔️ Backup from ${this.host.fullUrl} completed!`)); return new Blob([data]); } public async uploadBackup(backup: Blob): Promise { - Log.info(chalk.yellow(`➡️ Uploading backup to ${this.host.baseUrl}...`)); + Log.info(chalk.yellow(`➡️ Uploading backup to ${this.host.fullUrl}...`)); const form = this.generateForm(); form.append('action', 'in'); form.append('zip_file', backup, 'backup.tar.gz'); const uploadResponse = await this.fetch( - `${this.host.baseUrl}/admin/scripts/pi-hole/php/teleporter.php`, + `${this.host.fullUrl}/admin/scripts/pi-hole/php/teleporter.php`, { body: form, method: 'POST' @@ -120,21 +120,21 @@ export class Client { const uploadText = await uploadResponse.text(); if (uploadResponse.status !== 200 || !uploadText.endsWith('OK')) throw new ErrorNotification({ - message: `Failed to upload backup to "${this.host.baseUrl}".`, + message: `Failed to upload backup to "${this.host.fullUrl}".`, verbose: { - host: this.host.baseUrl, + host: this.host.fullUrl, status: uploadResponse.status, responseBody: uploadText } }); - Log.info(chalk.green(`✔️ Backup uploaded to ${this.host.baseUrl}!`)); + Log.info(chalk.green(`✔️ Backup uploaded to ${this.host.fullUrl}!`)); Log.verbose(`Result:\n${chalk.blue(uploadText)}`); if (Config.updateGravity) { - Log.info(chalk.yellow(`➡️ Updating gravity on ${this.host.baseUrl}...`)); + Log.info(chalk.yellow(`➡️ Updating gravity on ${this.host.fullUrl}...`)); const gravityUpdateResponse = await this.fetch( - `${this.host.baseUrl}/admin/scripts/pi-hole/php/gravity.sh.php`, + `${this.host.fullUrl}/admin/scripts/pi-hole/php/gravity.sh.php`, { method: 'GET' } ); @@ -146,15 +146,15 @@ export class Client { !updateText.endsWith('Pi-hole blocking is enabled') ) throw new ErrorNotification({ - message: `Failed updating gravity on "${this.host.baseUrl}".`, + message: `Failed updating gravity on "${this.host.fullUrl}".`, verbose: { - host: this.host.baseUrl, + host: this.host.fullUrl, status: gravityUpdateResponse.status, eventStream: updateText } }); - Log.info(chalk.green(`✔️ Gravity updated on ${this.host.baseUrl}!`)); + Log.info(chalk.green(`✔️ Gravity updated on ${this.host.fullUrl}!`)); Log.verbose(`Result:\n${chalk.blue(updateText)}`); } diff --git a/src/config.test.ts b/src/config.test.ts index 9295877f..218ebe4a 100644 --- a/src/config.test.ts +++ b/src/config.test.ts @@ -91,12 +91,14 @@ describe('Config', () => { const expected = { baseUrl: 'http://10.0.0.2', + fullUrl: 'http://10.0.0.2', + path: '', password: 'mypassword' }; - expect(Config.primaryHost).toStrictEqual(expected); + expect(Config.primaryHost).toEqual(expected); resetEnv(); - expect(Config.primaryHost).toStrictEqual(expected); + expect(Config.primaryHost).toEqual(expected); }); }); @@ -132,13 +134,15 @@ describe('Config', () => { const expected = [ { baseUrl: 'http://10.0.0.3', - password: 'mypassword' + password: 'mypassword', + fullUrl: 'http://10.0.0.3', + path: '' } ]; - expect(Config.secondaryHosts).toStrictEqual(expected); + expect(Config.secondaryHosts).toEqual(expected); resetEnv(); - expect(Config.secondaryHosts).toStrictEqual(expected); + expect(Config.secondaryHosts).toEqual(expected); }); test('should return multiple secondary hosts', () => { @@ -151,18 +155,24 @@ describe('Config', () => { process.env['SECONDARY_HOST_5_BASE_URL'] = 'http://10.0.0.7'; process.env['SECONDARY_HOST_5_PASSWORD'] = 'mypassword4'; - expect(Config.secondaryHosts).toStrictEqual([ + expect(Config.secondaryHosts).toEqual([ { baseUrl: 'http://10.0.0.3', - password: 'mypassword1' + password: 'mypassword1', + fullUrl: 'http://10.0.0.3', + path: '' }, { baseUrl: 'http://10.0.0.4', - password: 'mypassword2' + password: 'mypassword2', + fullUrl: 'http://10.0.0.4', + path: '' }, { baseUrl: 'http://10.0.0.5', - password: 'mypassword3' + password: 'mypassword3', + fullUrl: 'http://10.0.0.5', + path: '' } ]); }); diff --git a/src/config.ts b/src/config.ts index de45ad16..8421fda5 100644 --- a/src/config.ts +++ b/src/config.ts @@ -7,10 +7,10 @@ export class Config { private static _intervalMinutes?: number; static get primaryHost(): Host { - this._primaryHost ??= { - baseUrl: this.getRequiredEnv('PRIMARY_HOST_BASE_URL'), - password: this.getRequiredEnv('PRIMARY_HOST_PASSWORD') - }; + this._primaryHost ??= new Host( + this.getRequiredEnv('PRIMARY_HOST_BASE_URL'), + this.getRequiredEnv('PRIMARY_HOST_PASSWORD') + ); return this._primaryHost; } @@ -18,10 +18,10 @@ export class Config { static get secondaryHosts(): Host[] { if (!this._secondaryHosts) { this._secondaryHosts = [ - { - baseUrl: this.getRequiredEnv('SECONDARY_HOST_1_BASE_URL'), - password: this.getRequiredEnv('SECONDARY_HOST_1_PASSWORD') - } + new Host( + this.getRequiredEnv('SECONDARY_HOST_1_BASE_URL'), + this.getRequiredEnv('SECONDARY_HOST_1_PASSWORD') + ) ]; let count = 2; @@ -29,10 +29,11 @@ export class Config { process.env[`SECONDARY_HOST_${count}_BASE_URL`] !== undefined && process.env[`SECONDARY_HOST_${count}_PASSWORD`] !== undefined ) { - this._secondaryHosts.push({ - baseUrl: process.env[`SECONDARY_HOST_${count}_BASE_URL`]!, - password: process.env[`SECONDARY_HOST_${count}_PASSWORD`]! - }); + this._secondaryHosts.push( + new Host( + this.getRequiredEnv(`SECONDARY_HOST_${count}_BASE_URL`), + this.getRequiredEnv(`SECONDARY_HOST_${count}_PASSWORD`) + )); count++; } @@ -42,7 +43,7 @@ export class Config { } static get allHostBaseUrls(): string[] { - return [this.primaryHost, ...this.secondaryHosts].map((host) => host.baseUrl); + return [this.primaryHost, ...this.secondaryHosts].map((host) => host.fullUrl); } static get syncOptions(): SyncOptions { @@ -160,7 +161,18 @@ export interface SyncOptions { flushtables: boolean; } -export interface Host { - baseUrl: string; +export class Host { + private baseUrl: string; + private path: string; + fullUrl: string; password: string; + + constructor(baseUrl: string, password: string, path?: string) { + this.path = path ?? ''; + this.baseUrl = baseUrl; + this.password = password + this.fullUrl = this.baseUrl + this.path + } } + + diff --git a/src/sync.test.ts b/src/sync.test.ts index 4a361157..29d8a0c5 100644 --- a/src/sync.test.ts +++ b/src/sync.test.ts @@ -3,7 +3,7 @@ import chalk from 'chalk'; import nock from 'nock'; import { Blob } from 'node-fetch'; import { Client } from './client'; -import { Config } from './config'; +import { Config, Host } from './config'; import { Log } from './log'; import { ErrorNotification, Notify } from './notify'; import { Sync } from './sync'; @@ -20,19 +20,10 @@ describe('entrypoint', () => { let secondaryHostClient1: Client; let secondaryHostClient2: Client; - const primaryHostValue = { - baseUrl: 'http://10.0.0.2', - password: 'password1' - }; + const primaryHostValue = new Host('http://10.0.0.2', 'password1'); const secondaryHostsValue = [ - { - baseUrl: 'http://10.0.0.3', - password: 'password2' - }, - { - baseUrl: 'http://10.0.0.4', - password: 'password3' - } + new Host('http://10.0.0.3', 'password2'), + new Host('http://10.0.0.4', 'password3') ]; const backupData = new Blob([]);