模型与测试

优质
小牛编辑
122浏览
2023-12-01

安装 password-hash 与测试

用来处理用户密码,不存储用户的明文密码,而是存储加密后的。

npm i password-hash -S

在 src 目录下面创建 db.ts , 定义模型我们使用define方法, 这些都是有代码提示的。

import * as Sequelize from 'sequelize';
import * as ph from 'password-hash';
import { resolve } from 'path';

const sq = new Sequelize('db', null, null,{
    dialect: 'sqlite',
    storage: resolve(__dirname, '../storage/db.sqlite3')
});

var User = sq.define('user', {
    id:{
        type: Sequelize.INTEGER,
        primaryKey: true,
        autoIncrement: true
    },
    username: {
      type: Sequelize.STRING,
    },
    email: {
     type: Sequelize.STRING
    },
    password: {
     type: Sequelize.STRING
    }
}, {
    timestamps: false,
    freezeTableName: true // Model tableName will be the same as the model name
});

User.create({
    username: 'yugo',
    email: 'belovedyogurt@gmail.com',
    password: ph.generate('123456')
}).then(console.log)

首先编译

tsc

之后再运行

node dist/db.js

这样我们就可以在数据里面看到我们的数据了。

编写 Model 与 ava 测试文件

之前我们是把js文件编译到dist目录下管理,假如我们的test下面的测试文件也跑到dist目录下面的话就感觉很别扭,因为它没有在它该在的位置上,也就是不在其位而谋其政,尽管我们可以通过写2个 tsconfig.json 和 配置 exclude 选项让它们分开编译来解决这个问题,我们还不如就让 js 和 ts 在同一目录下来的简单。

修改我们的 db.ts 我们让他仅仅只提供数据库连接。

import * as Sequelize from 'sequelize';
import { resolve } from 'path';

const sq = new Sequelize('db', null, null,{
    dialect: 'sqlite',
    storage: resolve(__dirname, '../storage/db.sqlite3')
});

export default sq;

修改 tsconfig.json

在根目录下面新建一个 types 文件夹,在这里面你可以写你的代码提示文件,但是并不会被转换成模块。

{
    "compilerOptions": {
        "module": "commonjs",
        "target": "es5",
        "noImplicitAny": false,
        "sourceMap": true,
        "lib": [
            "es6",
            "es7"
        ],
        "typeRoots": [
            "./types"   
        ]
    }
}

在 src 目录下面创建 model 文件夹,新建 todo.tstodoFolder.tsuser.ts 三个文件。

model 下面都是存放我们的模型,通过 hasMany 定义模型之间的关系。

  • todo.ts
import sq from '../db';
import * as Sequelize from 'sequelize';
import { TodoFolder } from './todoFolder'

const Todo = sq.define<any, any>('todo', {
    id: {
        type: Sequelize.INTEGER,
        primaryKey: true,
        autoIncrement: true
    },
    text: {
        type: Sequelize.TEXT
    },
    completed: {
        type: Sequelize.BOOLEAN
    },
    'todo_folder_id': {
        type: Sequelize.INTEGER,
        references: {
            model: TodoFolder,
            key: 'id'
        }
    }
},{
    freezeTableName: true // 模型名字与表名相同
});

TodoFolder.hasMany(Todo, { as: 'Todos', foreignKey: 'todo_folder_id' })

export { Todo }
  • todoFolder.ts
import sq from '../db';
import * as Sequelize from 'sequelize';
import {User} from './user';

const TodoFolder = sq.define<any, any>('todo_folder', {
    id: {
        type: Sequelize.INTEGER,
        primaryKey: true,
        autoIncrement: true
    },
    title: {
        type: Sequelize.TEXT
    },
   'user_id': {
        type: Sequelize.INTEGER,
        references: {
            model: User,
            key: 'id'
        }
    }
},{
    timestamps: false, // 关闭时间戳
    freezeTableName: true // 模型名字与表名相同
})

User.hasMany(TodoFolder, { constraints: false, as: 'Folders', foreignKey: 'user_id' });

export { TodoFolder }
  • user.ts
import sq from '../db';
import * as Sequelize from 'sequelize';
import * as ph from "password-hash";

const User = sq.define<any, IUser>('user', {
    id:{
        type: Sequelize.INTEGER,
        primaryKey: true,
        autoIncrement: true
    },
    username: {
      type: Sequelize.STRING,
    },
    email: {
     type: Sequelize.STRING
    },
    password: {
     type: Sequelize.STRING
    }
}, {
    timestamps: false, // 关闭时间戳
    freezeTableName: true // 模型名字与表名相同
});


interface IUser {
    username: string,
    email: string,
    password: string
}

export default {
    async createUser(user: IUser) {
        return User.create({
            username: user.username,
            email: user.email,
            password: ph.generate(user.password)
        });
    },

    getOne: User.findById,

}

export { User }

为了确保这些 model 都已经正确可用了,我们需要编写我们的测试文件。

首先全局安装 ava 测试套件

npm i ava -g

在根目录初始化 ava 套件

ava --init

在根目录下面创建 test 文件夹,在里面新建 user.ts

一定不要把异步函数传给闭包,要不然有一些你意想不到的后果。

import UserUtil, { User } from '../src/model/user';
import { TodoFolder } from '../src/model/todoFolder';
import { Todo } from '../src/model/todo';

import test from 'ava';
import * as ph from 'password-hash';

// 错误的例子,最后一个 User 死活删除不了。
// async function deleteData(data: any[]){
//     if(data.length == 0) return;
//     data.forEach( async (i) => {
//         await i.destroy();
//     })
// }

async function deleteData(data: any[]){
    if(data.length == 0) return;
    const compose = data.map((i) => {
        return i.destroy();
    });
    await Promise.all(compose);
}

async function destroyAll(){
    // 因为外键依赖的原因,我们需要按顺序删除
    let todos = await Todo.findAll();

    let folders = await TodoFolder.findAll();

    let users = await User.findAll();

    await Promise.all([deleteData(todos), deleteData(folders), deleteData(users)])

    console.log('aleady delete all data\n');
}

async function fackerData(){
    const user = await UserUtil.createUser({
        username: 'yugo',
        email: 'belovedyogurt@gmail.com',
        password: '123456'
    });

    const todoFolder = TodoFolder.build({
        'user_id': user.id,
        title: '生活'
    });

    await todoFolder.save();

    const todo_1 = Todo.build({
        text: '吃大餐!',
        completed: true,
        'todo_folder_id': todoFolder.id
    });

    const todo_2 = Todo.build({
        text: '睡大觉!',
        completed: false,
        'todo_folder_id': todoFolder.id
    });

    await todo_1.save();
    await todo_2.save();

    console.log('facker data finished!\n');
}

test('test user create', async (t) => {

    await destroyAll();
    await fackerData();

    const user = await User.find({
        where: {
            email: 'belovedyogurt@gmail.com'
        }
    });


    let folders = await user.getFolders();

    let todos = await folders[0].getTodos();

    t.is(folders[0].title, '生活');

    t.is(todos[0].completed, true);
    t.is(todos[0].text, '吃大餐!');

    t.is(ph.verify('123456', user.password), true)

});

每次运行测试之前必须先运行 tsc 编译好,之后再运行 ava 命令。

我们可以修改一下 package.json 的 scripts

  "scripts": {
    "test": "tsc && ava",
    "tsc": "tsc --watch",
    "start": "pm2 start src/index.js --watch src"
  },

现在我们在根目录下运行

ava

到现在,我们的 Model 已经基本建立完成了,接下来我们进入业务编写环节。