Skip to content

Commit

Permalink
refactor: separate utility functions and business logic
Browse files Browse the repository at this point in the history
  • Loading branch information
johackim committed Apr 24, 2024
1 parent 095e17b commit 66dc0d8
Show file tree
Hide file tree
Showing 15 changed files with 150 additions and 107 deletions.
20 changes: 20 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: Test

on:
push:

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- uses: actions/setup-node@v3
with:
node-version: 20

- name: Install
run: yarn

- name: Test
run: npm test
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM node:18 AS build
FROM node:20 AS build

WORKDIR /app

Expand All @@ -12,7 +12,7 @@ RUN rm -rf node_modules

RUN yarn install --prod --ignore-optional

FROM gcr.io/distroless/nodejs:18
FROM gcr.io/distroless/nodejs20

WORKDIR /app

Expand Down
30 changes: 12 additions & 18 deletions __tests__/block.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,6 @@ import {
isDistractionBlocked,
} from '../src/block';

import('../src/socket');

jest.mock('child_process', () => ({
execSync: jest.fn().mockImplementation(() => false),
}));

beforeEach(() => {
config.blocklist = [];
config.whitelist = [];
Expand Down Expand Up @@ -41,13 +35,13 @@ test('Should check if a time is within an interval', async () => {
});

test('Should block a distraction', async () => {
blockDistraction({ name: 'example.com' });
await blockDistraction({ name: 'example.com' });

expect(isDistractionBlocked('example.com')).toEqual(true);
});

test('Should block a distraction with a duration', async () => {
blockDistraction({ name: 'twitter.com', time: '2m' });
await blockDistraction({ name: 'twitter.com', time: '2m' });

expect(isDistractionBlocked('twitter.com')).toBe(true);
expect(config.blocklist).toEqual([{ name: 'twitter.com', time: '2m', timeout: expect.any(Number) }]);
Expand All @@ -57,20 +51,20 @@ test('Should block a distraction with a time-based interval', async () => {
const currentDate = new Date('2021-01-01T12:00:00Z');
jest.spyOn(global, 'Date').mockImplementation(() => currentDate);

blockDistraction({ name: 'example.com', time: '0h-23h' });
await blockDistraction({ name: 'example.com', time: '0h-23h' });

expect(isDistractionBlocked('example.com')).toBe(true);
});

test('Should block a specific subdomain', async () => {
blockDistraction({ name: 'www.example.com' });
await blockDistraction({ name: 'www.example.com' });

expect(isDistractionBlocked('www.example.com')).toBe(true);
expect(isDistractionBlocked('example.com')).toBe(false);
});

test('Should block all subdomains of a domain with a wildcard', async () => {
blockDistraction({ name: '*.example.com' });
await blockDistraction({ name: '*.example.com' });

expect(isDistractionBlocked('www.example.com')).toBe(true);
});
Expand All @@ -79,13 +73,13 @@ test('Should block all subdomains of a domain with a wildcard & a time-based int
const currentDate = new Date('2021-01-01T12:00:00Z');
jest.spyOn(global, 'Date').mockImplementation(() => currentDate);

blockDistraction({ name: '*.example.com', time: '0h-19h' });
await blockDistraction({ name: '*.example.com', time: '0h-19h' });

expect(isDistractionBlocked('www.example.com')).toBe(true);
});

test('Should block all domains with *.*', async () => {
blockDistraction({ name: '*.*' });
await blockDistraction({ name: '*.*' });

expect(isDistractionBlocked('example.com')).toBe(true);
});
Expand All @@ -94,7 +88,7 @@ test('Should not block an app with a time-based interval', async () => {
const currentDate = new Date('2021-01-01T22:00:00Z');
jest.spyOn(global, 'Date').mockImplementation(() => currentDate);

blockDistraction({ name: 'chromium', time: '0h-20h' });
await blockDistraction({ name: 'chromium', time: '0h-20h' });

expect(isDistractionBlocked('chromium')).toBe(false);
});
Expand All @@ -103,21 +97,21 @@ test('Should not block a subdomain of a domain with a wildcard & a time-based in
const currentDate = new Date('2021-01-01T20:00:00Z');
jest.spyOn(global, 'Date').mockImplementation(() => currentDate);

blockDistraction({ name: '*.example.com', time: '0h-19h' });
await blockDistraction({ name: '*.example.com', time: '0h-19h' });

expect(isDistractionBlocked('www.example.com')).toBe(false);
});

test('Should not block apps if *.* is in the blocklist', async () => {
blockDistraction({ name: '*.*' });
await blockDistraction({ name: '*.*' });

expect(isDistractionBlocked('chromium')).toBe(false);
});

test('Should unblock a distraction', async () => {
blockDistraction({ name: 'example.com' });
await blockDistraction({ name: 'example.com' });

unblockDistraction({ name: 'example.com' });
await unblockDistraction({ name: 'example.com' });

expect(isDistractionBlocked('example.com')).toBe(false);
});
Expand Down
10 changes: 0 additions & 10 deletions __tests__/commands.spec.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,6 @@
import { config } from '../src/config';
import { helpCmd, versionCmd, blockCmd, whitelistCmd, unblockCmd, shieldCmd } from '../src/commands';

jest.mock('net', () => ({
createConnection: jest.fn().mockReturnThis(),
write: jest.fn(),
end: jest.fn(),
}));

jest.mock('child_process', () => ({
execSync: jest.fn().mockImplementation(() => false),
}));

beforeEach(() => {
process.argv = [];
config.blocklist = [];
Expand Down
4 changes: 2 additions & 2 deletions __tests__/daemon.spec.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import fs from 'fs';
import { config } from '../src/config';
import { config, readConfig } from '../src/config';
import { getRunningApps } from '../src/utils';
import { blockDistraction } from '../src/block';
import { handleAppBlocking, handleTimeout, updateResolvConf } from '../src/daemon';
Expand Down Expand Up @@ -47,5 +47,5 @@ test('Should remove a distraction from blocklist if timeout is reached', async (

handleTimeout();

expect(config.blocklist).toEqual([{ name: 'chromium' }]);
expect(readConfig().blocklist).toEqual([{ name: 'chromium' }]);
});
49 changes: 21 additions & 28 deletions __tests__/shield.spec.js
Original file line number Diff line number Diff line change
@@ -1,57 +1,50 @@
import { config } from '../src/config';
import { enableShieldMode, disableShieldMode } from '../src/shield';
import { readConfig } from '../src/config';
import { whitelistDistraction } from '../src/whitelist';
import { enableShieldMode, disableShieldMode } from '../src/shield';
import { blockDistraction, unblockDistraction } from '../src/block';

jest.mock('child_process', () => ({
execSync: jest.fn().mockImplementation(() => false),
}));

beforeEach(() => {
config.blocklist = [];
config.whitelist = [];
config.shield = false;
config.password = 'ulysse';
jest.spyOn(console, 'log').mockImplementation(() => {});
});

test('Should enable shield mode', async () => {
enableShieldMode('ulysse');
await enableShieldMode('ulysse');

const passwordHash = 'd97e609b03de7506d4be3bee29f2431b40e375b33925c2f7de5466ce1928da1b';
expect(config.passwordHash).toBe(passwordHash);
expect(config.shield).toBe(true);
const { password, passwordHash, shield } = readConfig();
expect(password).toBeUndefined();
expect(passwordHash).toBe('d97e609b03de7506d4be3bee29f2431b40e375b33925c2f7de5466ce1928da1b');
expect(shield).toBe(true);
});

test('Should disable shield mode', async () => {
enableShieldMode('ulysse');
await enableShieldMode('ulysse');

disableShieldMode('ulysse');
await disableShieldMode('ulysse');

expect(config.shield).toBe(false);
expect(readConfig().shield).toBe(false);
expect(readConfig().passwordHash).toBeUndefined();
});

test('Should not disable shield mode if bad password', async () => {
enableShieldMode('ulysse');

disableShieldMode('badpassword');
await enableShieldMode('ulysse');
await disableShieldMode('badpassword');

expect(config.shield).toBe(true);
expect(readConfig().shield).toBe(true);
});

test('Should not unblock a distraction if shield mode is enabled', async () => {
blockDistraction({ name: 'example.com' });
enableShieldMode('ulysse');
await blockDistraction({ name: 'example.com' });
await enableShieldMode('ulysse');

unblockDistraction({ name: 'example.com' });
await unblockDistraction({ name: 'example.com' });

expect(config.blocklist).toEqual([{ name: 'example.com' }]);
expect(readConfig().blocklist).toContainEqual({ name: 'example.com' });
});

test('Should not whitelist a distraction if shield mode is enabled', async () => {
enableShieldMode('ulysse');
await enableShieldMode('ulysse');

whitelistDistraction({ name: 'example.com' });
await whitelistDistraction({ name: 'example.com' });

expect(config.whitelist).toEqual([]);
expect(readConfig().whitelist).not.toContainEqual({ name: 'example.com' });
});
11 changes: 11 additions & 0 deletions jest.setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
require('dotenv/config');
const fs = require('fs');
const { CONFIG_PATH } = require('./src/constants');

module.exports = () => {
if (fs.existsSync(CONFIG_PATH)) {
fs.unlinkSync(CONFIG_PATH);
}

import('./src/socket');
};
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "ulysse",
"author": "johackim",
"description": "Simple CLI tool for blocking your distracting apps and websites",
"homepage": "https://github.com/johackim/ulysse",
"homepage": "https://github.com/getulysse/ulysse",
"license": "GPL-3.0",
"main": "dist/index.js",
"version": "0.4.2",
Expand All @@ -17,10 +17,10 @@
},
"repository": {
"type": "git",
"url": "git+https://github.com/johackim/ulysse.git"
"url": "git+https://github.com/getulysse/ulysse.git"
},
"bugs": {
"url": "https://github.com/johackim/ulysse/issues"
"url": "https://github.com/getulysse/ulysse/issues"
},
"keywords": [
"cli",
Expand Down Expand Up @@ -59,9 +59,10 @@
"prebuild": "rm -rf dist",
"build": "rollup --bundleConfigAsCjs -c",
"start": "babel-node src/index.js",
"test": "DOTENV_CONFIG_PATH=.env.test jest --runInBand --setupFiles dotenv/config --forceExit"
"test": "DOTENV_CONFIG_PATH=.env.test jest -i --setupFiles dotenv/config --forceExit"
},
"jest": {
"globalSetup": "./jest.setup.js",
"restoreMocks": true,
"transformIgnorePatterns": [],
"transform": {
Expand Down
8 changes: 4 additions & 4 deletions src/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { isDistractionWhitelisted } from './whitelist';
import { DOMAIN_REGEX } from './constants';
import { removeDuplicates, getRootDomain, getTimeType, createTimeout } from './utils';

export const blockDistraction = (distraction) => {
export const blockDistraction = async (distraction) => {
config.blocklist = removeDuplicates([...config.blocklist, distraction]);
config.blocklist = config.blocklist.map((d) => {
if (getTimeType(d.time) === 'duration') {
Expand All @@ -14,15 +14,15 @@ export const blockDistraction = (distraction) => {
return d;
});

editConfig(config);
await editConfig(config);
};

export const unblockDistraction = (distraction) => {
export const unblockDistraction = async (distraction) => {
if (config.shield) return;

config.blocklist = config.blocklist.filter(({ name, time }) => JSON.stringify({ name, time }) !== JSON.stringify(distraction));

editConfig(config);
await editConfig(config);
};

export const isValidDomain = (domain) => DOMAIN_REGEX.test(domain);
Expand Down
4 changes: 2 additions & 2 deletions src/commands.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import path from 'path';
import { isAbsolute } from 'path';
import { config } from './config';
import { getParam } from './utils';
import { version } from '../package.json';
Expand Down Expand Up @@ -60,7 +60,7 @@ export const whitelistCmd = (name) => {
const password = getParam('--password') || getParam('-p');
const distraction = { name, time };

if (!isValidDomain(name.replace('*.', '')) && !path.isAbsolute(name)) {
if (!isValidDomain(name.replace('*.', '')) && !isAbsolute(name)) {
console.log('You must provide a valid distraction.');
return;
}
Expand Down
26 changes: 18 additions & 8 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { dirname } from 'path';
import { isSudo, tryCatch } from './utils';
import { CONFIG_PATH, DEFAULT_CONFIG, SOCKET_PATH } from './constants';

export const sendDataToSocket = (data) => {
export const sendDataToSocket = (data) => new Promise((resolve, reject) => {
const client = net.createConnection(SOCKET_PATH);

if (typeof data === 'object') {
Expand All @@ -14,23 +14,33 @@ export const sendDataToSocket = (data) => {
}

client.end();
};

export const config = (tryCatch(() => {
client.on('end', resolve);

client.on('error', reject);
});

export const createConfig = () => {
if (!fs.existsSync(CONFIG_PATH)) {
fs.mkdirSync(dirname(CONFIG_PATH), { recursive: true });
fs.writeFileSync(CONFIG_PATH, JSON.stringify(DEFAULT_CONFIG, null, 4), 'utf8');
}
};

export const readConfig = () => {
createConfig();
return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
}, DEFAULT_CONFIG))();
};

export const editConfig = (newConfig) => {
export const editConfig = async (newConfig) => {
if (isSudo()) {
fs.writeFileSync(CONFIG_PATH, JSON.stringify(newConfig, null, 4), 'utf8');
} else {
sendDataToSocket(newConfig);
await sendDataToSocket(newConfig);
}

return newConfig;
};

export const config = (tryCatch(() => {
createConfig();
return readConfig();
}, DEFAULT_CONFIG))();
Loading

0 comments on commit 66dc0d8

Please sign in to comment.