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 23, 2024
1 parent 98289a2 commit 095e17b
Show file tree
Hide file tree
Showing 18 changed files with 635 additions and 675 deletions.
134 changes: 134 additions & 0 deletions __tests__/block.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { config } from '../src/config';
import {
blockDistraction,
isWithinTimeRange,
unblockDistraction,
isValidDistraction,
isDistractionBlocked,
} from '../src/block';

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

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

beforeEach(() => {
config.blocklist = [];
config.whitelist = [];
config.shield = false;
});

test('Should check a distraction', async () => {
expect(isValidDistraction({ name: '' })).toBe(false);
expect(isValidDistraction({ name: '*' })).toBe(false);
expect(isValidDistraction({ name: '*.*' })).toBe(true);
expect(isValidDistraction({ name: '*.example.com' })).toBe(true);
expect(isValidDistraction({ name: 'example.com' })).toBe(true);
expect(isValidDistraction({ name: 'chromium' })).toBe(true);
expect(isValidDistraction({ name: 'chromium', time: 'badtime' })).toBe(false);
expect(isValidDistraction({ name: 'chromium', time: '1m' })).toBe(true);
expect(isValidDistraction({ name: 'inexistent' })).toBe(false);
});

test('Should check if a time is within an interval', async () => {
const currentDate = new Date('2021-01-01T12:00:00Z');
jest.spyOn(global, 'Date').mockImplementation(() => currentDate);

expect(isWithinTimeRange('0h-23h')).toBe(true);
expect(isWithinTimeRange('0h-19h')).toBe(true);
expect(isWithinTimeRange('20h-23h')).toBe(false);
});

test('Should block a distraction', async () => {
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' });

expect(isDistractionBlocked('twitter.com')).toBe(true);
expect(config.blocklist).toEqual([{ name: 'twitter.com', time: '2m', timeout: expect.any(Number) }]);
});

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' });

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

test('Should block a specific subdomain', async () => {
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' });

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

test('Should block all subdomains of a domain with a wildcard & 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-19h' });

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

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

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

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' });

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

test('Should not block a subdomain of a domain with a wildcard & a time-based interval', async () => {
const currentDate = new Date('2021-01-01T20:00:00Z');
jest.spyOn(global, 'Date').mockImplementation(() => currentDate);

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: '*.*' });

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

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

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

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

test('Should run isDistractionBlocked in less than 150ms with a large blocklist', async () => {
config.blocklist = Array.from({ length: 500000 }, (_, i) => ({ name: `${i + 1}.com` }));

isDistractionBlocked('example.com');
const start = process.hrtime();
isDistractionBlocked('example.com');
const end = process.hrtime(start);

expect(end[1] / 1000000).toBeLessThan(150);
});
113 changes: 57 additions & 56 deletions __tests__/commands.spec.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { config, editConfig } from '../src/utils';
import { config } from '../src/config';
import { helpCmd, versionCmd, blockCmd, whitelistCmd, unblockCmd, shieldCmd } from '../src/commands';

jest.mock('net', () => ({
Expand All @@ -19,113 +19,114 @@ beforeEach(() => {
jest.spyOn(console, 'log').mockImplementation(() => {});
});

test('As a user, I can display the help', async () => {
test('Should display the help', async () => {
helpCmd();

expect(console.log).toHaveBeenCalledWith(expect.stringContaining('Usage: ulysse [OPTIONS]'));
});

test('As a user, I can block a domain', async () => {
test('Should display the version', async () => {
versionCmd();

expect(console.log).toHaveBeenCalledWith(expect.stringMatching(/\d+\.\d+\.\d+/));
});

test('Should block a domain', async () => {
blockCmd('example.com');

expect(console.log).toHaveBeenCalledWith('Blocking example.com');
});

test('As a user, I can block an app', async () => {
test('Should block an app', async () => {
blockCmd('chromium');

expect(console.log).toHaveBeenCalledWith('Blocking chromium');
});

test('As a user, I cannot block an invalid distraction', async () => {
blockCmd('inexistent');

expect(console.log).toHaveBeenCalledWith('You must provide a valid distraction.');
});

test('As a user, I can whitelist a domain', async () => {
whitelistCmd('youtube.com');

expect(console.log).toHaveBeenCalledWith('Whitelisting youtube.com');
});

test('As a user, I can whitelist a domain with a wildcard', async () => {
whitelistCmd('*.youtube.com');

expect(console.log).toHaveBeenCalledWith('Whitelisting *.youtube.com');
});

test('As a user, I can unblock a domain', async () => {
test('Should unblock a domain', async () => {
unblockCmd('example.com');

expect(console.log).toHaveBeenCalledWith('Unblocking example.com');
});

test('As a user, I can unblock an app', async () => {
test('Should unblock an app', async () => {
unblockCmd('chromium');

expect(console.log).toHaveBeenCalledWith('Unblocking chromium');
});

test('As a user, I can enable shield mode', async () => {
shieldCmd();
test('Should not block an invalid distraction', async () => {
blockCmd('inexistent');

expect(console.log).toHaveBeenCalledWith('Shield mode enabled.');
expect(console.log).toHaveBeenCalledWith('You must provide a valid distraction.');
});

test('As a user, I cannot enable shield mode if it is already enabled', async () => {
const passwordHash = 'd97e609b03de7506d4be3bee29f2431b40e375b33925c2f7de5466ce1928da1b';
editConfig({ shield: true, passwordHash });
test('Should not unblock a distraction if shield mode is enabled', async () => {
config.shield = true;
config.passwordHash = 'd97e609b03de7506d4be3bee29f2431b40e375b33925c2f7de5466ce1928da1b';

shieldCmd();
unblockCmd('youtube.com');

expect(console.log).toHaveBeenCalledWith('Shield mode already enabled.');
expect(console.log).toHaveBeenCalledWith('You must disable the shield mode first.');
});

test('As a user, I can disable shield mode', async () => {
process.argv = ['ulysse', '-s', 'off', '-p', 'ulysse'];
const passwordHash = 'd97e609b03de7506d4be3bee29f2431b40e375b33925c2f7de5466ce1928da1b';
editConfig({ shield: true, passwordHash });
test('Should not whitelist a distraction if shield mode is enabled', async () => {
config.shield = true;
config.passwordHash = 'd97e609b03de7506d4be3bee29f2431b40e375b33925c2f7de5466ce1928da1b';

shieldCmd('off');
whitelistCmd('youtube.com');

expect(console.log).toHaveBeenCalledWith('Shield mode disabled.');
expect(console.log).toHaveBeenCalledWith('You must disable the shield mode first.');
});

test('As a user, I cannot disable shield mode if it is already disabled', async () => {
editConfig({ shield: false, password: 'ulysse' });
test('Should not whitelist an app with a relative path', async () => {
whitelistCmd('signal-desktop');

expect(console.log).toHaveBeenCalledWith('You must provide a valid distraction.');
});

test('Should not disable shield mode if it is already disabled', async () => {
config.shield = false;
config.password = 'ulysse';

shieldCmd('off');

expect(console.log).toHaveBeenCalledWith('Shield mode already disabled.');
});

test('As a user, I cannot unblock a distraction if shield mode is enabled', async () => {
const passwordHash = 'd97e609b03de7506d4be3bee29f2431b40e375b33925c2f7de5466ce1928da1b';
editConfig({ shield: true, passwordHash });
test('Should not enable shield mode if it is already enabled', async () => {
config.shield = true;
config.passwordHash = 'd97e609b03de7506d4be3bee29f2431b40e375b33925c2f7de5466ce1928da1b';

unblockCmd('youtube.com');
shieldCmd('on');

expect(console.log).toHaveBeenCalledWith('You must disable the shield mode first.');
expect(console.log).toHaveBeenCalledWith('Shield mode already enabled.');
});

test('As a user, I cannot whitelist a distraction if shield mode is enabled', async () => {
const passwordHash = 'd97e609b03de7506d4be3bee29f2431b40e375b33925c2f7de5466ce1928da1b';
editConfig({ shield: true, passwordHash });

test('Should whitelist a domain', async () => {
whitelistCmd('youtube.com');

expect(console.log).toHaveBeenCalledWith('You must disable the shield mode first.');
expect(console.log).toHaveBeenCalledWith('Whitelisting youtube.com');
});

test('As a user, I cannot whitelist an app with a relative path', async () => {
whitelistCmd('signal-desktop');
test('Should whitelist a domain with a wildcard', async () => {
whitelistCmd('*.youtube.com');

expect(console.log).toHaveBeenCalledWith('You must provide a valid distraction.');
expect(console.log).toHaveBeenCalledWith('Whitelisting *.youtube.com');
});

test('As a user, I can display the version of Ulysse', async () => {
versionCmd();
test('Should enable shield mode', async () => {
shieldCmd();

expect(console.log).toHaveBeenCalledWith(expect.stringMatching(/\d+\.\d+\.\d+/));
expect(console.log).toHaveBeenCalledWith('Shield mode enabled.');
});

test('Should disable shield mode', async () => {
process.argv = ['ulysse', '-s', 'off', '-p', 'ulysse'];
config.shield = true;
config.passwordHash = 'd97e609b03de7506d4be3bee29f2431b40e375b33925c2f7de5466ce1928da1b';

shieldCmd('off');

expect(console.log).toHaveBeenCalledWith('Shield mode disabled.');
});
59 changes: 31 additions & 28 deletions __tests__/daemon.spec.js
Original file line number Diff line number Diff line change
@@ -1,48 +1,51 @@
import fs from 'fs';
import * as Utils from '../src/utils';

jest.mock('../src/utils');

jest.mock('dgram', () => ({
createSocket: jest.fn().mockReturnThis(),
bind: jest.fn().mockReturnThis(),
on: jest.fn(),
}));

jest.mock('net', () => ({
createServer: jest.fn().mockReturnThis(),
listen: jest.fn().mockReturnThis(),
import { config } from '../src/config';
import { getRunningApps } from '../src/utils';
import { blockDistraction } from '../src/block';
import { handleAppBlocking, handleTimeout, updateResolvConf } from '../src/daemon';

jest.mock('../src/utils', () => ({
...jest.requireActual('../src/utils'),
isSudo: jest.fn().mockImplementation(() => true),
}));

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

jest.mock('socket.io-client', () => ({
io: jest.fn(() => ({
emit: jest.fn(),
on: jest.fn(),
})),
exec: jest.fn().mockImplementation(() => false),
}));

beforeEach(() => {
config.blocklist = [];
config.whitelist = [];
config.shield = false;
jest.spyOn(console, 'log').mockImplementation(() => {});
jest.spyOn(Utils, 'isSudo').mockReturnValue(true);
jest.spyOn(Utils, 'getRunningBlockedApps').mockReturnValue([{ name: 'chromium', pid: 123 }]);
jest.spyOn(Utils, 'updateResolvConf').mockImplementation(() => {
fs.writeFileSync(process.env.RESOLV_CONF_PATH, 'nameserver 127.0.0.1', 'utf8');
});
});

test('Should block a running app', async () => {
await import('../src/daemon');
blockDistraction({ name: 'node' });

handleAppBlocking();

expect(console.log).toHaveBeenCalledWith('Blocking chromium');
expect(console.log).toHaveBeenCalledWith('Blocking node');
});

test('Should get all running apps', async () => {
const apps = getRunningApps();

expect(JSON.stringify(apps)).toContain('node');
});

test('Should edit /etc/resolv.conf', async () => {
await import('../src/daemon');
updateResolvConf('127.0.0.1');

expect(fs.existsSync(process.env.RESOLV_CONF_PATH)).toBe(true);
expect(fs.readFileSync(process.env.RESOLV_CONF_PATH, 'utf8')).toBe('nameserver 127.0.0.1');
});

test('Should remove a distraction from blocklist if timeout is reached', async () => {
config.blocklist = [{ name: 'chromium' }, { name: 'example.com', timeout: 1708617136 }];

handleTimeout();

expect(config.blocklist).toEqual([{ name: 'chromium' }]);
});
Loading

0 comments on commit 095e17b

Please sign in to comment.