Adding tests, bumping packages with audit warnings
This commit is contained in:
2060
package-lock.json
generated
2060
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -75,16 +75,20 @@
|
||||
"@feathersjs/rest-client": "^5.0.8",
|
||||
"@types/cookie-parser": "^1.4.3",
|
||||
"@types/express-session": "^1.17.7",
|
||||
"@types/jsdom": "^27.0.0",
|
||||
"@types/mocha": "^10.0.1",
|
||||
"@types/node": "^20.4.5",
|
||||
"@types/redis": "^4.0.11",
|
||||
"@types/sinon": "^21.0.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"husky": "^8.0.3",
|
||||
"jsdom": "^27.0.1",
|
||||
"lint-staged": "^13.2.3",
|
||||
"mocha": "^10.2.0",
|
||||
"nodemon": "^3.0.1",
|
||||
"prettier": "^3.0.0",
|
||||
"shx": "^0.3.4",
|
||||
"sinon": "^21.0.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.1.6"
|
||||
},
|
||||
|
||||
@@ -37,4 +37,37 @@ describe('Feathers application tests', () => {
|
||||
assert.strictEqual(response?.data?.name, 'NotFound');
|
||||
}
|
||||
});
|
||||
|
||||
it('requires authentication for aliases endpoint', async () => {
|
||||
try {
|
||||
await axios.get(`${appUrl}/aliases`, {
|
||||
responseType: 'json',
|
||||
});
|
||||
assert.fail('should never get here');
|
||||
} catch (error: any) {
|
||||
const { response } = error;
|
||||
assert.strictEqual(response?.status, 401);
|
||||
assert.strictEqual(response?.data?.code, 401);
|
||||
assert.strictEqual(response?.data?.name, 'NotAuthenticated');
|
||||
}
|
||||
});
|
||||
|
||||
it('serves static files from public directory', async () => {
|
||||
const { data } = await axios.get<string>(`${appUrl}/index.html`);
|
||||
|
||||
assert.ok(data.indexOf('<html lang="en">') !== -1);
|
||||
assert.ok(data.indexOf('WildDuck') !== -1);
|
||||
});
|
||||
|
||||
it('has all services registered', () => {
|
||||
assert.ok(app.service('aliases'), 'Aliases service registered');
|
||||
assert.ok(app.service('auth-oidc'), 'Auth-OIDC service registered');
|
||||
assert.ok(app.service('auth-oidc/callback'), 'Auth-OIDC callback service registered');
|
||||
});
|
||||
|
||||
it('has correct configuration', () => {
|
||||
assert.strictEqual(typeof app.get('port'), 'number', 'Port is a number');
|
||||
assert.strictEqual(typeof app.get('host'), 'string', 'Host is a string');
|
||||
assert.strictEqual(app.get('port'), 8998, 'Port is 8998 in test mode');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,11 +1,340 @@
|
||||
// For more information about this file see https://dove.feathersjs.com/guides/cli/service.test.html
|
||||
import assert from 'assert';
|
||||
import { app } from '../../../src/app';
|
||||
import * as sinon from 'sinon';
|
||||
import config from 'config';
|
||||
import { faker } from '@faker-js/faker';
|
||||
import wildDuckClient from '../../../src/clients/wildduck.client';
|
||||
|
||||
describe('aliases service', () => {
|
||||
let sandbox: sinon.SinonSandbox;
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.createSandbox();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('registered the service', () => {
|
||||
const service = app.service('aliases');
|
||||
|
||||
assert.ok(service, 'Registered the service');
|
||||
});
|
||||
|
||||
it('creates a new alias successfully', async () => {
|
||||
const service = app.service('aliases');
|
||||
|
||||
const configGetStub = sandbox.stub(config, 'get');
|
||||
configGetStub.withArgs('wildDuck.domain').returns('example.com');
|
||||
configGetStub.withArgs('wildDuck.preferredDomain').returns('example.com');
|
||||
|
||||
sandbox.stub(faker.git, 'commitSha').returns('abcd');
|
||||
sandbox.stub(faker.color, 'human').returns('red');
|
||||
sandbox.stub(faker.animal, 'snake').returns('snake');
|
||||
|
||||
sandbox.stub(wildDuckClient, 'get').callsFake((url: string) => {
|
||||
if (url === 'addresses/resolve/user@example.com') {
|
||||
return Promise.resolve({
|
||||
data: {
|
||||
success: true,
|
||||
user: 'user123',
|
||||
id: 'addr123',
|
||||
address: 'user@example.com',
|
||||
main: true,
|
||||
tags: [],
|
||||
created: '2025-01-01',
|
||||
},
|
||||
});
|
||||
}
|
||||
if (url === '/users/user123/addresses') {
|
||||
return Promise.resolve({
|
||||
data: {
|
||||
success: true,
|
||||
results: [
|
||||
{
|
||||
success: true,
|
||||
id: 'alias456',
|
||||
address: 'red-snake-abcd@example.com',
|
||||
main: false,
|
||||
user: 'user123',
|
||||
tags: ['test'],
|
||||
created: '2025-12-09T10:00:00Z',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
}
|
||||
return Promise.reject(new Error(`Unexpected URL: ${url}`));
|
||||
});
|
||||
|
||||
sandbox.stub(wildDuckClient, 'post').resolves({
|
||||
data: {
|
||||
success: true,
|
||||
id: 'alias456',
|
||||
},
|
||||
});
|
||||
|
||||
const params = {
|
||||
session: {
|
||||
user: {
|
||||
emails: ['user@example.com'],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const data = { tags: ['test'] };
|
||||
|
||||
const result = await service.create(data, params);
|
||||
|
||||
assert.ok(Array.isArray(result), 'Result should be an array');
|
||||
assert.strictEqual(result.length, 1, 'Result should contain one alias');
|
||||
assert.strictEqual(
|
||||
result[0].address,
|
||||
'red-snake-abcd@example.com',
|
||||
'Alias should have the expected address'
|
||||
);
|
||||
assert.deepStrictEqual(result[0].tags, ['test'], 'Alias should have the correct tags');
|
||||
assert.ok(result[0].created, 'Alias should have a created date');
|
||||
assert.strictEqual(result[0].id, 'alias456', 'Removable alias should have an id');
|
||||
});
|
||||
|
||||
it('finds user aliases successfully', async () => {
|
||||
const service = app.service('aliases');
|
||||
|
||||
const configGetStub = sandbox.stub(config, 'get');
|
||||
configGetStub.withArgs('wildDuck.domain').returns('example.com');
|
||||
configGetStub.withArgs('wildDuck.preferredDomain').returns('example.com');
|
||||
|
||||
sandbox.stub(wildDuckClient, 'get').callsFake((url: string) => {
|
||||
if (url === 'addresses/resolve/user@example.com') {
|
||||
return Promise.resolve({
|
||||
data: {
|
||||
success: true,
|
||||
user: 'user123',
|
||||
id: 'addr123',
|
||||
address: 'user@example.com',
|
||||
main: true,
|
||||
tags: [],
|
||||
created: '2025-01-01',
|
||||
},
|
||||
});
|
||||
}
|
||||
if (url === '/users/user123/addresses') {
|
||||
return Promise.resolve({
|
||||
data: {
|
||||
success: true,
|
||||
results: [
|
||||
{
|
||||
success: true,
|
||||
id: 'alias456',
|
||||
address: 'red-snake-abcd@example.com',
|
||||
main: false,
|
||||
user: 'user123',
|
||||
tags: ['test'],
|
||||
created: '2025-12-09T10:00:00Z',
|
||||
},
|
||||
{
|
||||
success: true,
|
||||
id: null,
|
||||
address: 'user@example.com',
|
||||
main: true,
|
||||
user: 'user123',
|
||||
tags: [],
|
||||
created: '2025-01-01',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
}
|
||||
return Promise.reject(new Error(`Unexpected URL: ${url}`));
|
||||
});
|
||||
|
||||
const params = {
|
||||
session: {
|
||||
user: {
|
||||
emails: ['user@example.com'],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = await service.find(params);
|
||||
|
||||
assert.ok(Array.isArray(result), 'Result should be an array');
|
||||
assert.strictEqual(result.length, 2, 'Result should contain two aliases');
|
||||
assert.strictEqual(result[0].address, 'red-snake-abcd@example.com', 'First alias address');
|
||||
assert.strictEqual(result[0].id, 'alias456', 'Removable alias has id');
|
||||
assert.strictEqual(result[1].address, 'user@example.com', 'Second alias address');
|
||||
assert.strictEqual(result[1].id, null, 'Main alias has no id');
|
||||
});
|
||||
|
||||
it('removes an alias successfully', async () => {
|
||||
const service = app.service('aliases');
|
||||
|
||||
const configGetStub = sandbox.stub(config, 'get');
|
||||
configGetStub.withArgs('wildDuck.domain').returns('example.com');
|
||||
configGetStub.withArgs('wildDuck.preferredDomain').returns('example.com');
|
||||
|
||||
sandbox.stub(wildDuckClient, 'get').callsFake((url: string) => {
|
||||
if (url === 'addresses/resolve/user@example.com') {
|
||||
return Promise.resolve({
|
||||
data: {
|
||||
success: true,
|
||||
user: 'user123',
|
||||
id: 'addr123',
|
||||
address: 'user@example.com',
|
||||
main: true,
|
||||
tags: [],
|
||||
created: '2025-01-01',
|
||||
},
|
||||
});
|
||||
}
|
||||
if (url === 'addresses/resolve/alias456') {
|
||||
return Promise.resolve({
|
||||
data: {
|
||||
success: true,
|
||||
id: 'alias456',
|
||||
address: 'red-snake-abcd@example.com',
|
||||
main: false,
|
||||
user: 'user123',
|
||||
tags: ['test'],
|
||||
created: '2025-12-09T10:00:00Z',
|
||||
},
|
||||
});
|
||||
}
|
||||
if (url === '/users/user123/addresses') {
|
||||
return Promise.resolve({
|
||||
data: {
|
||||
success: true,
|
||||
results: [],
|
||||
},
|
||||
});
|
||||
}
|
||||
return Promise.reject(new Error(`Unexpected URL: ${url}`));
|
||||
});
|
||||
|
||||
sandbox.stub(wildDuckClient, 'delete').resolves({
|
||||
data: {
|
||||
success: true,
|
||||
},
|
||||
});
|
||||
|
||||
const params = {
|
||||
session: {
|
||||
user: {
|
||||
emails: ['user@example.com'],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = await service.remove('alias456', params);
|
||||
|
||||
assert.ok(Array.isArray(result), 'Result should be an array');
|
||||
assert.strictEqual(result.length, 0, 'Result should be empty after deletion');
|
||||
});
|
||||
|
||||
it('throws error when creating alias fails', async () => {
|
||||
const service = app.service('aliases');
|
||||
|
||||
const configGetStub = sandbox.stub(config, 'get');
|
||||
configGetStub.withArgs('wildDuck.domain').returns('example.com');
|
||||
configGetStub.withArgs('wildDuck.preferredDomain').returns('example.com');
|
||||
|
||||
sandbox.stub(faker.git, 'commitSha').returns('abcd');
|
||||
sandbox.stub(faker.color, 'human').returns('red');
|
||||
sandbox.stub(faker.animal, 'snake').returns('snake');
|
||||
|
||||
sandbox.stub(wildDuckClient, 'get').callsFake((url: string) => {
|
||||
if (url === 'addresses/resolve/user@example.com') {
|
||||
return Promise.resolve({
|
||||
data: {
|
||||
success: true,
|
||||
user: 'user123',
|
||||
id: 'addr123',
|
||||
address: 'user@example.com',
|
||||
main: true,
|
||||
tags: [],
|
||||
created: '2025-01-01',
|
||||
},
|
||||
});
|
||||
}
|
||||
return Promise.reject(new Error(`Unexpected URL: ${url}`));
|
||||
});
|
||||
|
||||
sandbox.stub(wildDuckClient, 'post').resolves({
|
||||
data: {
|
||||
success: false,
|
||||
},
|
||||
});
|
||||
|
||||
const params = {
|
||||
session: {
|
||||
user: {
|
||||
emails: ['user@example.com'],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const data = { tags: ['test'] };
|
||||
|
||||
try {
|
||||
await service.create(data, params);
|
||||
assert.fail('Should have thrown an error');
|
||||
} catch (error: any) {
|
||||
assert.strictEqual(error.message, 'Failed to create alias');
|
||||
}
|
||||
});
|
||||
|
||||
it('throws error when removing alias with invalid domain', async () => {
|
||||
const service = app.service('aliases');
|
||||
|
||||
const configGetStub = sandbox.stub(config, 'get');
|
||||
configGetStub.withArgs('wildDuck.domain').returns('example.com');
|
||||
configGetStub.withArgs('wildDuck.preferredDomain').returns('example.com');
|
||||
|
||||
sandbox.stub(wildDuckClient, 'get').callsFake((url: string) => {
|
||||
if (url === 'addresses/resolve/user@example.com') {
|
||||
return Promise.resolve({
|
||||
data: {
|
||||
success: true,
|
||||
user: 'user123',
|
||||
id: 'addr123',
|
||||
address: 'user@example.com',
|
||||
main: true,
|
||||
tags: [],
|
||||
created: '2025-01-01',
|
||||
},
|
||||
});
|
||||
}
|
||||
if (url === 'addresses/resolve/alias456') {
|
||||
return Promise.resolve({
|
||||
data: {
|
||||
success: true,
|
||||
id: 'alias456',
|
||||
address: 'red-snake-abcd@invalid.com',
|
||||
main: false,
|
||||
user: 'user123',
|
||||
tags: ['test'],
|
||||
created: '2025-12-09T10:00:00Z',
|
||||
},
|
||||
});
|
||||
}
|
||||
return Promise.reject(new Error(`Unexpected URL: ${url}`));
|
||||
});
|
||||
|
||||
const params = {
|
||||
session: {
|
||||
user: {
|
||||
emails: ['user@example.com'],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
await service.remove('alias456', params);
|
||||
assert.fail('Should have thrown an error');
|
||||
} catch (error: any) {
|
||||
assert.strictEqual(error.message, 'Unable to delete address');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,11 +1,63 @@
|
||||
// For more information about this file see https://dove.feathersjs.com/guides/cli/service.test.html
|
||||
import assert from 'assert';
|
||||
import { app } from '../../../src/app';
|
||||
import * as sinon from 'sinon';
|
||||
import config from 'config';
|
||||
import { Issuer, generators } from 'openid-client';
|
||||
|
||||
describe('auth-oidc service', () => {
|
||||
let sandbox: sinon.SinonSandbox;
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.createSandbox();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('registered the service', () => {
|
||||
const service = app.service('auth-oidc');
|
||||
|
||||
assert.ok(service, 'Registered the service');
|
||||
});
|
||||
|
||||
it('initiates OIDC authentication flow', async () => {
|
||||
const service = app.service('auth-oidc');
|
||||
|
||||
const configGetStub = sandbox.stub(config, 'get');
|
||||
configGetStub.withArgs('oidc.gatewayUri').returns('https://oidc.example.com');
|
||||
configGetStub.withArgs('oidc.clientId').returns('client123');
|
||||
configGetStub.withArgs('oidc.clientSecret').returns('secret123');
|
||||
configGetStub.withArgs('oidc.redirectUris').returns(['https://app.example.com/auth-oidc/callback']);
|
||||
configGetStub.withArgs('oidc.responseTypes').returns('code');
|
||||
configGetStub.withArgs('oidc.signedResponseAlg').returns('RS256');
|
||||
configGetStub.withArgs('oidc.authMethod').returns('client_secret_basic');
|
||||
configGetStub.withArgs('clientUrl').returns('https://app.example.com');
|
||||
configGetStub.withArgs('oidc.scopes').returns('openid profile email');
|
||||
configGetStub.withArgs('oidc.codeChallengeMethod').returns('S256');
|
||||
|
||||
const mockClient = {
|
||||
authorizationUrl: sandbox.stub().returns('https://oidc.example.com/auth?code_challenge=abc123'),
|
||||
};
|
||||
|
||||
const mockIssuer = {
|
||||
Client: sandbox.stub().returns(mockClient),
|
||||
};
|
||||
|
||||
sandbox.stub(Issuer, 'discover').resolves(mockIssuer as any);
|
||||
|
||||
sandbox.stub(generators, 'codeVerifier').returns('verifier123');
|
||||
sandbox.stub(generators, 'codeChallenge').returns('challenge123');
|
||||
|
||||
const params = {
|
||||
session: {} as any,
|
||||
};
|
||||
|
||||
const result = await service.find(params);
|
||||
|
||||
assert.strictEqual(typeof result, 'string', 'Result should be a string URL');
|
||||
assert.ok(result.includes('https://oidc.example.com/auth'), 'Result should be the auth URL');
|
||||
assert.strictEqual(params.session.codeVerifier, 'verifier123', 'Code verifier should be stored in session');
|
||||
assert.ok(mockClient.authorizationUrl.calledOnce, 'Authorization URL should be generated');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,11 +1,71 @@
|
||||
// For more information about this file see https://dove.feathersjs.com/guides/cli/service.test.html
|
||||
import assert from 'assert';
|
||||
import { app } from '../../../../src/app';
|
||||
import * as sinon from 'sinon';
|
||||
import config from 'config';
|
||||
import { Issuer } from 'openid-client';
|
||||
|
||||
describe('auth-oidc/callback service', () => {
|
||||
let sandbox: sinon.SinonSandbox;
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.createSandbox();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('registered the service', () => {
|
||||
const service = app.service('auth-oidc/callback');
|
||||
|
||||
assert.ok(service, 'Registered the service');
|
||||
});
|
||||
|
||||
it('completes OIDC authentication and sets user session', async () => {
|
||||
const service = app.service('auth-oidc/callback');
|
||||
|
||||
const configGetStub = sandbox.stub(config, 'get');
|
||||
configGetStub.withArgs('oidc.gatewayUri').returns('https://oidc.example.com');
|
||||
configGetStub.withArgs('oidc.clientId').returns('client123');
|
||||
configGetStub.withArgs('oidc.clientSecret').returns('secret123');
|
||||
configGetStub.withArgs('oidc.redirectUris').returns(['https://app.example.com/auth-oidc/callback']);
|
||||
configGetStub.withArgs('clientUrl').returns('https://app.example.com');
|
||||
|
||||
const mockTokenSet = {
|
||||
access_token: 'access123',
|
||||
id_token: 'id123',
|
||||
};
|
||||
|
||||
const mockUserinfo = {
|
||||
sub: 'user123',
|
||||
email: 'user@example.com',
|
||||
name: 'Test User',
|
||||
};
|
||||
|
||||
const mockClient = {
|
||||
callback: sandbox.stub().resolves(mockTokenSet),
|
||||
userinfo: sandbox.stub().resolves(mockUserinfo),
|
||||
};
|
||||
|
||||
const mockIssuer = {
|
||||
Client: sandbox.stub().returns(mockClient),
|
||||
};
|
||||
|
||||
sandbox.stub(Issuer, 'discover').resolves(mockIssuer as any);
|
||||
|
||||
const params = {
|
||||
session: { codeVerifier: 'verifier123' } as any,
|
||||
query: {
|
||||
iss: 'https://oidc.example.com',
|
||||
code: 'authcode123',
|
||||
},
|
||||
};
|
||||
|
||||
const result = await service.find(params);
|
||||
|
||||
assert.strictEqual(result, '/', 'Result should be the redirect path');
|
||||
assert.deepStrictEqual(params.session.user, mockUserinfo, 'User info should be stored in session');
|
||||
assert.ok(mockClient.callback.calledOnce, 'Callback should be called');
|
||||
assert.ok(mockClient.userinfo.calledOnce, 'Userinfo should be fetched');
|
||||
});
|
||||
});
|
||||
|
||||
106
test/ui.test.ts
Normal file
106
test/ui.test.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import assert from 'assert';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
describe('UI tests', () => {
|
||||
let html: string;
|
||||
|
||||
before(async () => {
|
||||
// Load the HTML file
|
||||
const htmlPath = path.join(__dirname, '../public/index.html');
|
||||
html = fs.readFileSync(htmlPath, 'utf8');
|
||||
});
|
||||
|
||||
it('HTML structure is valid and properly semantic', () => {
|
||||
// DOCTYPE and language
|
||||
assert.ok(html.includes('<!DOCTYPE html>'), 'DOCTYPE declaration present');
|
||||
assert.ok(html.match(/<html\s+lang="en">/), 'HTML element with lang attribute');
|
||||
|
||||
// Head section - metadata and critical dependencies
|
||||
assert.ok(html.includes('<head>'), 'Head section exists');
|
||||
assert.ok(html.includes('<title>WildDuck Aliases</title>'), 'Title element with correct text');
|
||||
assert.ok(html.includes('content="Aliases for Wild Duck"'), 'Description meta tag present');
|
||||
assert.ok(html.includes('name="viewport"'), 'Viewport meta tag for responsiveness');
|
||||
assert.ok(html.includes('content="width=device-width, initial-scale=1"'), 'Viewport allows responsive design');
|
||||
|
||||
// Bootstrap integration
|
||||
assert.ok(html.includes('bootstrap@5.3.2/dist/css/bootstrap.min.css'), 'Bootstrap CSS linked with version');
|
||||
assert.ok(html.match(/integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV\/Dwwykc2MPK8M2HN"/),
|
||||
'Bootstrap CSS has SRI integrity check');
|
||||
assert.ok(html.includes('bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js'), 'Bootstrap JS bundle linked');
|
||||
assert.ok(html.match(/integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN\/o0jlpcV8Qyq46cDfL"/),
|
||||
'Bootstrap JS has SRI integrity check');
|
||||
|
||||
// Template engine
|
||||
assert.ok(html.includes('handlebars@latest/dist/handlebars.js'), 'Handlebars library linked');
|
||||
|
||||
// Application script
|
||||
assert.ok(html.includes('src="index.js"'), 'Application JS file referenced');
|
||||
assert.ok(html.includes('async'), 'Script tag has async attribute for non-blocking load');
|
||||
|
||||
// Body structure - semantic layout
|
||||
assert.ok(html.includes('<body>'), 'Body element exists');
|
||||
assert.ok(html.includes('class="wrapper"'), 'Wrapper container for layout');
|
||||
assert.ok(html.includes('class="header"'), 'Header section');
|
||||
assert.ok(html.includes('id="container"'), 'Main container for dynamic content');
|
||||
|
||||
// Image and heading
|
||||
assert.ok(html.includes('class="logo"'), 'Logo image with class');
|
||||
assert.ok(html.includes('src="logo.jpg"'), 'Logo source specified');
|
||||
assert.ok(html.includes('<h1>WildDuck Aliases</h1>'), 'Main heading with correct text');
|
||||
|
||||
// Ensure no structural errors (closing tags)
|
||||
const bodyCount = (html.match(/<body>/g) || []).length;
|
||||
const bodyCloseCount = (html.match(/<\/body>/g) || []).length;
|
||||
assert.strictEqual(bodyCount, bodyCloseCount, 'Body tags properly closed');
|
||||
|
||||
const htmlCount = (html.match(/<html/g) || []).length;
|
||||
const htmlCloseCount = (html.match(/<\/html>/g) || []).length;
|
||||
assert.strictEqual(htmlCount, htmlCloseCount, 'HTML tags properly closed');
|
||||
});
|
||||
|
||||
it('JavaScript file exists and has expected functions', () => {
|
||||
const jsPath = path.join(__dirname, '../public/index.js');
|
||||
const js = fs.readFileSync(jsPath, 'utf8');
|
||||
|
||||
assert.ok(js.includes('function renderAliases'), 'renderAliases function exists');
|
||||
assert.ok(js.includes('function createAlias'), 'createAlias function exists');
|
||||
assert.ok(js.includes('function deleteAlias'), 'deleteAlias function exists');
|
||||
assert.ok(js.includes('function copyToClipboard'), 'copyToClipboard function exists');
|
||||
assert.ok(js.includes('fetch(\'/aliases\')'), 'Initial fetch call exists');
|
||||
});
|
||||
|
||||
it('CSS defines valid styling for page layout and responsiveness', () => {
|
||||
const styleMatch = html.match(/<style>([\s\S]*?)<\/style>/);
|
||||
assert.ok(styleMatch, 'Style block exists');
|
||||
const styles = styleMatch[1];
|
||||
|
||||
// Universal reset - critical for consistent styling
|
||||
assert.ok(/\*\s*{[\s\S]*?margin:\s*0[\s\S]*?padding:\s*0[\s\S]*?box-sizing:\s*border-box/.test(styles),
|
||||
'Universal reset defines margin, padding, and box-sizing');
|
||||
|
||||
// HTML/Body layout - ensures proper container sizing
|
||||
assert.ok(/html\s*{[\s\S]*?height:\s*100%/.test(styles),
|
||||
'HTML height set to 100%');
|
||||
assert.ok(/body\s*{[\s\S]*?min-height:\s*100%[\s\S]*?display:\s*flex[\s\S]*?justify-content:\s*center/.test(styles),
|
||||
'Body uses flexbox for centering (min-height, display, justify-content)');
|
||||
|
||||
// Logo responsive styling
|
||||
assert.ok(/img\.logo\s*{[\s\S]*?display:\s*block[\s\S]*?width:\s*30%[\s\S]*?max-width:\s*100%[\s\S]*?max-height:\s*100%/.test(styles),
|
||||
'Logo is block-level and responsive with width/max constraints');
|
||||
|
||||
// Text alignment
|
||||
assert.ok(/\.wrapper\s*{[\s\S]*?text-align:\s*center/.test(styles),
|
||||
'Wrapper centers text content');
|
||||
assert.ok(/\.table\s*{[\s\S]*?text-align:\s*left/.test(styles),
|
||||
'Table content is left-aligned for readability');
|
||||
|
||||
// Container spacing
|
||||
assert.ok(/#container\s*{[\s\S]*?padding-top:\s*50px/.test(styles),
|
||||
'Container has top padding for spacing');
|
||||
|
||||
// Bootstrap CSS is loaded
|
||||
assert.ok(html.includes('bootstrap@5.3.2/dist/css/bootstrap.min.css'),
|
||||
'Bootstrap CSS is linked from CDN');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user