398 lines
14 KiB
TypeScript
398 lines
14 KiB
TypeScript
import config from "config";
|
|
|
|
import Model from "../src/db/Model.js";
|
|
import ModelFactory from "../src/db/ModelFactory.js";
|
|
import {ManyThroughModelRelation, OneModelRelation} from "../src/db/ModelRelation.js";
|
|
import MysqlConnectionManager from "../src/db/MysqlConnectionManager.js";
|
|
import {ValidationBag} from "../src/db/Validator.js";
|
|
import {logger} from "../src/Logger.js";
|
|
import {MIGRATIONS} from "../src/TestApp.js";
|
|
|
|
class FakeDummyModel extends Model {
|
|
public id?: number = undefined;
|
|
public name?: string = undefined;
|
|
public date?: Date = undefined;
|
|
public date_default?: Date = undefined;
|
|
|
|
protected init(): void {
|
|
this.setValidation('name').acceptUndefined().between(3, 256);
|
|
}
|
|
}
|
|
|
|
class Post extends Model {
|
|
public id?: number = undefined;
|
|
public author_id?: number = undefined;
|
|
public content?: string = undefined;
|
|
|
|
public readonly author = new OneModelRelation(this, Author, {
|
|
localKey: 'author_id',
|
|
foreignKey: 'id',
|
|
});
|
|
|
|
protected init(): void {
|
|
this.setValidation('author_id').defined().exists(Author, 'id');
|
|
}
|
|
}
|
|
|
|
class Author extends Model {
|
|
public id?: number = undefined;
|
|
public name?: string = undefined;
|
|
|
|
public readonly roles = new ManyThroughModelRelation(this, Role, {
|
|
localKey: 'id',
|
|
foreignKey: 'id',
|
|
pivotTable: 'author_role',
|
|
localPivotKey: 'author_id',
|
|
foreignPivotKey: 'role_id',
|
|
});
|
|
}
|
|
|
|
class Role extends Model {
|
|
public id?: number = undefined;
|
|
public name?: string = undefined;
|
|
|
|
public readonly permissions = new ManyThroughModelRelation(this, Permission, {
|
|
localKey: 'id',
|
|
foreignKey: 'id',
|
|
pivotTable: 'role_permission',
|
|
localPivotKey: 'role_id',
|
|
foreignPivotKey: 'permission_id',
|
|
});
|
|
}
|
|
|
|
class Permission extends Model {
|
|
public id?: number = undefined;
|
|
public name?: string = undefined;
|
|
}
|
|
|
|
class AuthorRole extends Model {
|
|
public static get table(): string {
|
|
return 'author_role';
|
|
}
|
|
|
|
public author_id?: number = undefined;
|
|
public role_id?: number = undefined;
|
|
|
|
protected init(): void {
|
|
this.setValidation('author_id').defined().exists(Author, 'id');
|
|
this.setValidation('role_id').defined().exists(Role, 'id');
|
|
}
|
|
}
|
|
|
|
class RolePermission extends Model {
|
|
public static get table(): string {
|
|
return 'role_permission';
|
|
}
|
|
|
|
public role_id?: number = undefined;
|
|
public permission_id?: number = undefined;
|
|
|
|
protected init(): void {
|
|
this.setValidation('role_id').defined().exists(Role, 'id');
|
|
this.setValidation('permission_id').defined().exists(Permission, 'id');
|
|
}
|
|
}
|
|
|
|
let fakeDummyModelModelFactory: ModelFactory<FakeDummyModel>;
|
|
|
|
let postFactory: ModelFactory<Post>;
|
|
let authorFactory: ModelFactory<Author>;
|
|
let roleFactory: ModelFactory<Role>;
|
|
let permissionFactory: ModelFactory<Permission>;
|
|
|
|
beforeAll(async () => {
|
|
await MysqlConnectionManager.prepare();
|
|
await MysqlConnectionManager.query('DROP DATABASE IF EXISTS ' + config.get<string>('mysql.database'));
|
|
await MysqlConnectionManager.endPool();
|
|
|
|
logger.setSettings({minLevel: "trace"});
|
|
MysqlConnectionManager.registerMigrations(MIGRATIONS);
|
|
ModelFactory.register(FakeDummyModel);
|
|
ModelFactory.register(Post);
|
|
ModelFactory.register(Author);
|
|
ModelFactory.register(Role);
|
|
ModelFactory.register(Permission);
|
|
ModelFactory.register(AuthorRole);
|
|
ModelFactory.register(RolePermission);
|
|
await MysqlConnectionManager.prepare();
|
|
|
|
// Create FakeDummyModel table
|
|
fakeDummyModelModelFactory = ModelFactory.get(FakeDummyModel);
|
|
postFactory = ModelFactory.get(Post);
|
|
authorFactory = ModelFactory.get(Author);
|
|
roleFactory = ModelFactory.get(Role);
|
|
permissionFactory = ModelFactory.get(Permission);
|
|
|
|
await MysqlConnectionManager.query(`DROP TABLE IF EXISTS author_role`);
|
|
await MysqlConnectionManager.query(`DROP TABLE IF EXISTS role_permission`);
|
|
for (const factory of [
|
|
fakeDummyModelModelFactory,
|
|
postFactory,
|
|
authorFactory,
|
|
roleFactory,
|
|
permissionFactory,
|
|
]) {
|
|
await MysqlConnectionManager.query(`DROP TABLE IF EXISTS ${factory.table}`);
|
|
}
|
|
|
|
await MysqlConnectionManager.query(`CREATE TABLE ${fakeDummyModelModelFactory.table}(
|
|
id INT NOT NULL AUTO_INCREMENT,
|
|
name VARCHAR(256),
|
|
date DATETIME,
|
|
date_default DATETIME DEFAULT NOW(),
|
|
PRIMARY KEY(id)
|
|
)`);
|
|
|
|
await MysqlConnectionManager.query(`CREATE TABLE ${authorFactory.table}(
|
|
id INT NOT NULL AUTO_INCREMENT,
|
|
name VARCHAR(64),
|
|
PRIMARY KEY(id)
|
|
)`);
|
|
await MysqlConnectionManager.query(`CREATE TABLE ${postFactory.table}(
|
|
id INT NOT NULL AUTO_INCREMENT,
|
|
author_id INT NOT NULL,
|
|
content VARCHAR(512),
|
|
PRIMARY KEY(id),
|
|
FOREIGN KEY post_author_fk (author_id) REFERENCES ${authorFactory.table} (id)
|
|
)`);
|
|
await MysqlConnectionManager.query(`CREATE TABLE ${roleFactory.table}(
|
|
id INT NOT NULL AUTO_INCREMENT,
|
|
name VARCHAR(64),
|
|
PRIMARY KEY(id)
|
|
)`);
|
|
await MysqlConnectionManager.query(`CREATE TABLE ${permissionFactory.table}(
|
|
id INT NOT NULL AUTO_INCREMENT,
|
|
name VARCHAR(64),
|
|
PRIMARY KEY(id)
|
|
)`);
|
|
|
|
await MysqlConnectionManager.query(`CREATE TABLE author_role(
|
|
id INT NOT NULL AUTO_INCREMENT,
|
|
author_id INT NOT NULL,
|
|
role_id INT NOT NULL,
|
|
PRIMARY KEY(id),
|
|
FOREIGN KEY author_role_author_fk (author_id) REFERENCES ${authorFactory.table} (id),
|
|
FOREIGN KEY author_role_role_fk (role_id) REFERENCES ${roleFactory.table} (id)
|
|
)`);
|
|
await MysqlConnectionManager.query(`CREATE TABLE role_permission(
|
|
id INT NOT NULL AUTO_INCREMENT,
|
|
role_id INT NOT NULL,
|
|
permission_id INT NOT NULL,
|
|
PRIMARY KEY(id),
|
|
FOREIGN KEY role_permission_role_fk (role_id) REFERENCES ${roleFactory.table} (id),
|
|
FOREIGN KEY role_permission_permission_fk (permission_id) REFERENCES ${permissionFactory.table} (id)
|
|
)`);
|
|
|
|
|
|
/// SEED ///
|
|
|
|
// permissions
|
|
createPostPermission = Permission.create({name: 'create-post'});
|
|
await createPostPermission.save();
|
|
|
|
moderatePostPermission = Permission.create({name: 'moderate-post'});
|
|
await moderatePostPermission.save();
|
|
|
|
viewLogsPermission = Permission.create({name: 'view-logs'});
|
|
await viewLogsPermission.save();
|
|
|
|
|
|
// roles
|
|
guestRole = Role.create({name: 'guest'});
|
|
await guestRole.save();
|
|
await RolePermission.create({role_id: guestRole.id, permission_id: createPostPermission.id}).save();
|
|
|
|
moderatorRole = Role.create({name: 'moderator'});
|
|
await moderatorRole.save();
|
|
await RolePermission.create({role_id: moderatorRole.id, permission_id: createPostPermission.id}).save();
|
|
await RolePermission.create({role_id: moderatorRole.id, permission_id: moderatePostPermission.id}).save();
|
|
|
|
adminRole = Role.create({name: 'admin'});
|
|
await adminRole.save();
|
|
await RolePermission.create({role_id: adminRole.id, permission_id: createPostPermission.id}).save();
|
|
await RolePermission.create({role_id: adminRole.id, permission_id: moderatePostPermission.id}).save();
|
|
await RolePermission.create({role_id: adminRole.id, permission_id: viewLogsPermission.id}).save();
|
|
|
|
|
|
// authors
|
|
glimmerAuthor = Author.create({name: 'glimmer'});
|
|
await glimmerAuthor.save();
|
|
await AuthorRole.create({author_id: glimmerAuthor.id, role_id: guestRole.id}).save();
|
|
|
|
bowAuthor = Author.create({name: 'bow'});
|
|
await bowAuthor.save();
|
|
await AuthorRole.create({author_id: bowAuthor.id, role_id: moderatorRole.id}).save();
|
|
|
|
adoraAuthor = Author.create({name: 'adora'});
|
|
await adoraAuthor.save();
|
|
await AuthorRole.create({author_id: adoraAuthor.id, role_id: adminRole.id}).save();
|
|
|
|
|
|
// posts
|
|
post1 = Post.create({author_id: glimmerAuthor.id, content: 'I\'m the queen now and you\'ll do as I order.'});
|
|
await post1.save();
|
|
|
|
post2 = Post.create({author_id: adoraAuthor.id, content: 'But you\'re wrong!'});
|
|
await post2.save();
|
|
|
|
post3 = Post.create({author_id: bowAuthor.id, content: 'Come on guys, let\'s talk this through.'});
|
|
await post3.save();
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await MysqlConnectionManager.endPool();
|
|
});
|
|
|
|
describe('Model', () => {
|
|
it('should construct properly', () => {
|
|
const date = new Date(888);
|
|
const model = fakeDummyModelModelFactory.create({
|
|
name: 'a_name',
|
|
date: date,
|
|
non_existing_property: 'dropped_value',
|
|
}, true);
|
|
|
|
expect(model.id).toBeUndefined();
|
|
expect(model.name).toBe('a_name');
|
|
expect(model.date).toBe(date);
|
|
expect(model.date_default).toBeUndefined();
|
|
expect(model.non_existing_property).toBeUndefined();
|
|
});
|
|
|
|
it('should have a proper table name', () => {
|
|
expect(fakeDummyModelModelFactory.table).toBe('fake_dummy_models');
|
|
expect(FakeDummyModel.table).toBe('fake_dummy_models');
|
|
expect(FakeDummyModel.create({}).table).toBe('fake_dummy_models');
|
|
});
|
|
|
|
it('should insert properly', async () => {
|
|
const date = new Date(569985);
|
|
const insertInstance: FakeDummyModel | null = fakeDummyModelModelFactory.create({
|
|
name: 'name1',
|
|
date: date,
|
|
}, true);
|
|
|
|
|
|
// Insert
|
|
expect(insertInstance.exists()).toBeFalsy();
|
|
await insertInstance.save();
|
|
expect(insertInstance.exists()).toBeTruthy();
|
|
|
|
expect(insertInstance.id).toBe(1); // Auto id from insert
|
|
expect(insertInstance.name).toBe('name1');
|
|
expect(insertInstance.date?.getTime()).toBeCloseTo(date.getTime(), -4);
|
|
expect(insertInstance.date_default).toBeDefined();
|
|
|
|
// Check that row exists in DB
|
|
const retrievedInstance = await FakeDummyModel.getById(1);
|
|
expect(retrievedInstance).toBeDefined();
|
|
expect(retrievedInstance?.id).toBe(1);
|
|
expect(retrievedInstance?.name).toBe('name1');
|
|
expect(retrievedInstance?.date?.getTime()).toBeCloseTo(date.getTime(), -4);
|
|
expect(retrievedInstance?.date_default).toBeDefined();
|
|
|
|
const failingInsertModel = fakeDummyModelModelFactory.create({
|
|
name: 'a',
|
|
}, true);
|
|
await expect(failingInsertModel.save()).rejects.toBeInstanceOf(ValidationBag);
|
|
});
|
|
|
|
it('should update properly', async () => {
|
|
const insertModel = fakeDummyModelModelFactory.create({
|
|
name: 'update',
|
|
}, true);
|
|
await insertModel.save();
|
|
|
|
const preUpdatedModel = await FakeDummyModel.getById(insertModel.id);
|
|
expect(preUpdatedModel).not.toBeNull();
|
|
expect(preUpdatedModel?.name).toBe(insertModel.name);
|
|
|
|
// Update model
|
|
if (preUpdatedModel) {
|
|
preUpdatedModel.name = 'updated_name';
|
|
await preUpdatedModel.save();
|
|
}
|
|
|
|
const postUpdatedModel = await FakeDummyModel.getById(insertModel.id);
|
|
expect(postUpdatedModel).not.toBeNull();
|
|
expect(postUpdatedModel?.id).toBe(insertModel.id);
|
|
expect(postUpdatedModel?.name).not.toBe(insertModel.name);
|
|
expect(postUpdatedModel?.name).toBe(preUpdatedModel?.name);
|
|
});
|
|
|
|
it('should delete properly', async () => {
|
|
const insertModel = fakeDummyModelModelFactory.create({
|
|
name: 'delete',
|
|
}, true);
|
|
await insertModel.save();
|
|
|
|
const preDeleteModel = await FakeDummyModel.getById(insertModel.id);
|
|
expect(preDeleteModel).not.toBeNull();
|
|
|
|
await preDeleteModel?.delete();
|
|
|
|
const postDeleteModel = await FakeDummyModel.getById(insertModel.id);
|
|
expect(postDeleteModel).toBeNull();
|
|
});
|
|
});
|
|
|
|
let createPostPermission: Permission;
|
|
let moderatePostPermission: Permission;
|
|
let viewLogsPermission: Permission;
|
|
let guestRole: Role;
|
|
let moderatorRole: Role;
|
|
let adminRole: Role;
|
|
let glimmerAuthor: Author;
|
|
let bowAuthor: Author;
|
|
let adoraAuthor: Author;
|
|
let post1: Post;
|
|
let post2: Post;
|
|
let post3: Post;
|
|
|
|
describe('ModelRelation', () => {
|
|
test('Query and check relations', async () => {
|
|
const posts = await Post.select()
|
|
.with('author.roles.permissions')
|
|
.sortBy('id', 'ASC')
|
|
.get();
|
|
|
|
expect(posts.length).toBe(3);
|
|
|
|
async function testPost(
|
|
post: Post,
|
|
originalPost: Post,
|
|
expectedAuthor: Author,
|
|
expectedRoles: Role[],
|
|
expectedPermissions: Permission[],
|
|
) {
|
|
console.log('Testing post', post);
|
|
expect(post.id).toBe(originalPost.id);
|
|
expect(post.content).toBe(originalPost.content);
|
|
|
|
const actualAuthor = await post.author.get();
|
|
expect(actualAuthor).not.toBeNull();
|
|
expect(await post.author.has(expectedAuthor)).toBeTruthy();
|
|
expect(actualAuthor?.equals(expectedAuthor)).toBe(true);
|
|
|
|
const authorRoles = await actualAuthor?.roles.get() || [];
|
|
console.log('Roles:');
|
|
expect(authorRoles.map(r => r.id)).toStrictEqual(expectedRoles.map(r => r.id));
|
|
|
|
const authorPermissions = (await Promise.all(authorRoles.map(async r => await r.permissions.get())))
|
|
.flatMap(p => p);
|
|
console.log('Permissions:');
|
|
expect(authorPermissions.map(p => p.id)).toStrictEqual(expectedPermissions.map(p => p.id));
|
|
}
|
|
|
|
await testPost(posts[0], post1, glimmerAuthor,
|
|
[guestRole],
|
|
[createPostPermission]);
|
|
await testPost(posts[1], post2, adoraAuthor,
|
|
[adminRole],
|
|
[createPostPermission, moderatePostPermission, viewLogsPermission]);
|
|
await testPost(posts[2], post3, bowAuthor,
|
|
[moderatorRole],
|
|
[createPostPermission, moderatePostPermission]);
|
|
});
|
|
});
|