1
0
mirror of https://github.com/Foltik/Shimapan synced 2024-09-21 11:11:22 -04:00
shimapan/test/api.js

269 lines
10 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

process.env.NODE_ENV = 'test';
const chai = require('chai');
chai.use(require('chai-http'));
const should = chai.should();
const User = require('../app/models/User.js');
const Invite = require('../app/models/Invite.js');
const Upload = require('../app/models/Upload.js');
const util = require('./testUtil.js');
const canonicalize = require('../app/util/canonicalize').canonicalize;
let app;
let server;
let agent;
before(() => {
const main = require('../server.js');
app = main.app;
server = main.server;
agent = chai.request.agent(app);
});
after(() => {
server.close();
});
describe('Users', function() {
beforeEach(async () => util.clearDatabase());
describe('/POST register', () => {
describe('0 Valid Request', () => {
async function verifySuccessfulRegister(user) {
await util.createTestInvite();
const res = await util.registerUser(user, agent);
res.should.have.status(200);
res.body.should.be.a('object');
res.body.should.have.property('message').eql('Registration successful.');
const userCount = await User.countDocuments({username: user.username});
userCount.should.eql(1);
const inviteCount = await Invite.countDocuments({code: user.invite, recipient: canonicalize(user.username)});
inviteCount.should.eql(1);
}
it('MUST register a valid user with a valid invite', async () =>
verifySuccessfulRegister({username: 'user', password: 'pass', invite: 'code'})
);
it('MUST register a username with unicode symbols and a valid invite', async () =>
verifySuccessfulRegister({username: 'ᴮᴵᴳᴮᴵᴿᴰ', password: 'pass', invite: 'code'})
);
});
describe('1 Invalid Invites', () => {
async function verifyRejectedInvite(invite, message) {
const user = {username: 'user', password: 'pass', invite: 'code'};
if (invite) {
await util.createInvite(invite, agent);
user.invite = invite.code;
}
const res = await(util.registerUser(user, agent));
res.should.have.status(422);
res.body.should.be.a('object');
res.body.should.have.property('message').eql(message);
}
it('MUST NOT register a nonexistant invite', async () =>
verifyRejectedInvite(null, 'Invalid invite code.')
);
it('MUST NOT register a used invite', async () =>
verifyRejectedInvite({code: 'code', used: new Date()}, 'Invite already used.')
);
it('MUST NOT register an expired invite', async () =>
verifyRejectedInvite({code: 'code', exp: new Date()}, 'Invite expired.')
);
});
describe('2 Invalid Usernames', () => {
async function verifyRejectedUsername(user, message) {
const res = await util.registerUser(user, agent);
res.should.have.status(422);
res.body.should.be.a('object');
res.body.should.have.property('message').eql(message);
}
it('MUST NOT register a duplicate username', async () => {
await util.createTestInvites(2);
const user0 = {username: 'user', password: 'pass', invite: 'code0'};
const user1 = {username: 'user', password: 'diff', invite: 'code1'};
await util.registerUser(user0, agent);
return verifyRejectedUsername(user1, 'Username in use.');
});
it('MUST NOT register a username with a duplicate canonical name', async () => {
await util.createTestInvites(2);
const user0 = {username: 'bigbird', password: 'pass', invite: 'code0'};
const user1 = {username: 'ᴮᴵᴳᴮᴵᴿᴰ', password: 'diff', invite: 'code1'};
await util.registerUser(user0, agent);
return verifyRejectedUsername(user1, 'Username in use.');
});
it('MUST NOT register a username containing whitespace', async () => {
await util.createTestInvites(3);
const users = [
{username: 'user name', password: 'pass', invite: 'code0'},
{username: 'user name', password: 'pass', invite: 'code1'},
{username: 'user name', password: 'pass', invite: 'code2'}
];
const failMsg = 'Username contains invalid characters.';
return Promise.all(users.map(user => verifyRejectedUsername(user, failMsg)));
});
it('MUST NOT register a username containing HTML', async () => {
await util.createTestInvite();
const user = {username: 'user<svg/onload=alert("XSS")>', password: 'pass', invite: 'code'};
return verifyRejectedUsername(user, 'Username contains invalid characters.');
});
it('MUST NOT register a username with too many characters', async () => {
await util.createTestInvite();
const user = {username: '123456789_123456789_123456789_1234567', password: 'pass', invite: 'code'};
return verifyRejectedUsername(user, 'Username too long.');
})
});
});
describe('/POST login', () => {
async function verifySuccessfulLogin(credentials) {
const res = await util.login(credentials, agent);
res.should.have.status(200);
res.body.should.have.property('message').equal('Logged in.');
res.should.have.cookie('session.id');
const whoami = await util.whoami(agent);
whoami.should.have.status(200);
}
async function verifyFailedLogin(credentials) {
const res = await util.login(credentials, agent);
res.should.have.status(401);
res.body.should.be.a('object');
res.body.should.have.property('message').equal('Unauthorized.');
}
describe('0 Valid Request', () => {
it('SHOULD accept a valid user with a valid password', async () => {
await util.createTestUser(agent);
return verifySuccessfulLogin({username: 'user', password: 'pass'});
});
it('SHOULD accept any non-normalized variant of a username with a valid password', async () => {
await util.create
})
});
describe('1 Invalid Password', () => {
it('SHOULD NOT accept an invalid password', async () => {
await util.createTestUser(agent);
return verifyFailedLogin({username: 'user', password: 'bogus'});
});
});
describe('2 Invalid User', () => {
it('SHOULD NOT accept an invalid user', async () =>
verifyFailedLogin({username: 'bogus', password: 'bogus'})
);
});
});
});
describe('Uploads', () => {
beforeEach(async () => util.clearDatabase());
describe('/POST upload', () => {
async function verifySuccessfulUpload(file) {
const res = await util.upload(file, agent);
res.should.have.status(200);
res.body.should.be.a('object');
res.body.should.have.property('url');
res.body.should.have.property('id').match(/^[a-z]{6}$/);
}
async function verifyFailedUpload(file, status, message) {
const res = await util.upload(file, agent);
res.should.have.status(status);
res.body.should.be.a('object');
res.body.should.have.property('message').equal(message);
}
describe('0 Valid Request', () => {
it('SHOULD accept logged in valid upload', async () => {
await Promise.all([
util.createTestSession(agent),
util.createTestFile(2048, 'test.bin')
]);
await verifySuccessfulUpload('test.bin');
return Promise.all([
util.logout(agent),
util.deleteTestFile('test.bin')
]);
});
});
describe('1 Invalid Authentication', () => {
it('SHOULD NOT accept an unauthenticated request', async () =>
verifyFailedUpload(null, 401, 'Unauthorized.')
);
it('SHOULD NOT accept a request without file.upload scope', async () => {
await util.createInvite({code: 'code', scope: []});
await util.registerUser({username: 'user', password: 'pass', invite: 'code'}, agent);
await util.login({username: 'user', password: 'pass'}, agent);
await util.createTestFile(2048, 'test.bin');
await verifyFailedUpload('test.bin', 403, 'Forbidden.');
return Promise.all([
util.logout(agent),
util.deleteTestFile('test.bin')
]);
});
});
describe('3 Invalid File', () => {
it('SHOULD NOT accept a too large file', async () => {
await Promise.all([
util.createTestSession(agent),
util.createTestFile(1024 * 1024 * 129, 'large.bin') // 129 MiB, limit is 128 MiB
]);
await verifyFailedUpload('large.bin', 413, 'File too large.');
return Promise.all([
util.logout(agent),
util.deleteTestFile('large.bin')
]);
});
});
describe('4 Invalid Request', () => {
it('SHOULD NOT accept a request with no file attached', async () => {
await util.createTestSession(agent);
await verifyFailedUpload(null, 400, 'No file specified.');
return util.logout(agent);
})
})
});
});
after(() => server.close(() => process.exit(0)));