swaf/test/Model.test.ts

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