review

In the last section, we talked about the entire lifecycle of use case execution. As I came to think of it later, it would have taken a lot of time to write if it had to be fully compatible with code generation from the start. In order to give you a good experience, you still have to eat one bite at a time, not all at once.

So let's finish the code free mode first

Design a few core tables roughly

In order to facilitate the management of our use cases in the future, we will definitely need to do some classification and permission control, so we will first design several core tables:

  • The project table

    Use cases are separated in each project, similar to the concept of directory. Different use cases are located in different projects, which facilitates the classification of use cases.

  • Project Role table

    Each project needs to have its own members. For members, their responsibilities are different because of their different identities. We give several simple identities, such as:

    Group leader and group members, in which group leader can change the relevant permissions of group members.

  • Test case table

    It stores the information of each use case and is the smallest execution unit of the test platform.

The project table models/project. Py

from app.models import db
from datetime import datetime


class Project(db.Model) :
    id = db.Column(db.INT, primary_key=True)
    name = db.Column(db.String(16), unique=True, index=True)
    owner = db.Column(db.INT)
    created_at = db.Column(db.DATETIME, nullable=False)
    updated_at = db.Column(db.DATETIME, nullable=False)
    deleted_at = db.Column(db.DATETIME)
    create_user = db.Column(db.INT, nullable=True)
    update_user = db.Column(db.INT, nullable=True)
    private = db.Column(db.BOOLEAN, default=False)

    def __init__(self, name, owner, create_user, private=False) :
        self.name = name
        self.owner = owner
        self.private = private
        self.created_at = datetime.now()
        self.updated_at = datetime.now()
        self.create_user = create_user
        self.update_user = create_user
        self.deleted_at = None

Copy the code

In addition to creation/update time/user, there are also the following fields:

Owner (group leader) Name (project name) private

Project role table Models/project_role-py

from app.models import db
from datetime import datetime


class ProjectRole(db.Model) :
    id = db.Column(db.INT, primary_key=True)
    project_id = db.Column(db.INT, index=True)
    project_role = db.Column(db.INT, index=True)
    created_at = db.Column(db.DATETIME, nullable=False)
    updated_at = db.Column(db.DATETIME, nullable=False)
    deleted_at = db.Column(db.DATETIME)
    create_user = db.Column(db.INT, nullable=True)
    update_user = db.Column(db.INT, nullable=True)

    def __init__(self, project_id, project_role, create_user) :
        self.project_id = project_id
        self.project_role = project_role
        self.created_at = datetime.now()
        self.updated_at = datetime.now()
        self.create_user = create_user
        self.update_user = create_user
        self.deleted_at = None

Copy the code

Similar to the previous form, project_id is the project ID and project_Role is the project role.

We stipulate that:

1: group leader 2: group member

Test case table Models /test_case.py

from app.models import db
from datetime import datetime


class TestCase(db.Model) :
    id = db.Column(db.INT, primary_key=True)
    name = db.Column(db.String(32), unique=True, index=True)
    request_type = db.Column(db.INT, default=1, comment=Request type 1: HTTP 2: GRPC 3: Dubbo)
    url = db.Column(db.TEXT, nullable=False, comment="The request url")
    request_method = db.Column(db.String(12), nullable=True, comment="Request mode, null if non-HTTP")
    request_header = db.Column(db.TEXT, comment="Request header, nullable")
    project_id = db.Column(db.INT, comment="Project")
    tag = db.Column(db.String(64), comment="Use Case label")
    status = db.Column(db.INT, comment="Use case status: 1: pending 2: temporarily closed 3: Normal operation")
    expected = db.Column(db.TEXT, comment="Expected results, support for EL expressions", nullable=False)
    created_at = db.Column(db.DATETIME, nullable=False)
    updated_at = db.Column(db.DATETIME, nullable=False)
    deleted_at = db.Column(db.DATETIME)
    create_user = db.Column(db.INT, nullable=False)
    update_user = db.Column(db.INT, nullable=False)

    def __init__(self, name, request_type, url, project_id, tag, status, expected, create_user, request_header=None,
                 request_method=None) :self.name = name self.request_type = request_type self.url = url self.project_id = project_id self.tag = tag self.status  = status self.expected = expected self.create_user = create_user self.update_user = create_user self.request_header = request_header self.request_method = request_method self.created_at = datetime.now() self.updated_at = datetime.now()Copy the code

Basically, the content is in the comment, you can just watch, and there is no more explanation. Certainly there are some features that are not considered, after the order can be added to solve the field.

Write a decorator that checks user permissions

Create_user = update_user; create_user = update_user; create_user = update_user;

Remember the method you wrote earlier to resolve tokens? Let’s write a decorator that will automatically determine whether a user’s token is valid and get user information.

If the permission is not within the specified range, the normal member cannot invoke this interface. If the permission is not within the specified range, the normal member cannot invoke this interface.

Complete code:

from functools import wraps

from flask import request, jsonify

from app import pity
from app.middleware.Jwt import UserToken

FORBIDDEN = "Sorry, you don't have enough clearance."


class SingletonDecorator:
    def __init__(self, cls) :
        self.cls = cls
        self.instance = None

    def __call__(self, *args, **kwargs) :
        if self.instance is None:
            self.instance = self.cls(*args, **kwargs)
        return self.instance


def permission(role=pity.config.get("GUEST")) :
    def login_required(func) :
        @wraps(func)
        def wrapper(*args, **kwargs) :
            try:
                headers = request.headers
                token = headers.get('token')
                if token is None:
                    return jsonify(dict(code=401, msg="User information authentication failed, please check"))
                user_info = UserToken.parse_token(token)
                Write user information to kwargs
                kwargs["user_info"] = user_info
            except Exception as e:
                return jsonify(dict(code=401, msg=str(e)))
            # check whether the user has sufficient permissions, if not, return directly, do not continue
            if user_info.get("role".0) < role:
                return jsonify(dict(code=400, msg=FORBIDDEN))
            return func(*args, **kwargs)

        return wrapper

    return login_required

Copy the code

kwargs["user_info"] = user_info

Focus on this sentence.

Add permissions to the HTTP/Request interface we wrote earlier

Because our interface temporarily supports login users, so the default permission is ok, that is, user role is 0(ordinary user)!

The important thing to note here is that our http_request must have a user_info parameter, otherwise we will get an error because we may need user information later.

Revamp the front-end code

Since our front-end will not bring user token when requesting the interface, we need to transform the front-end request method:

  1. Send HTTP requests with token data
  2. Verification returns code, if it is 401, the user will be automatically logged out, indicating that the user has not logged in!
  • Create a SRC/utils/auth. Js
import { message } from 'antd';

export default {
  headers: (json = true) = > {
    const token = localStorage.getItem('pityToken');
    const headers = { token };
    if (json) {
      headers['Content-Type'] = 'application/json';
    }
    return headers;
  },
  response: (res, info = false) = > {
    if (res.code === 0) {
      if (info) {
        message.info(res.msg);
      }
      return true;
    }
    if (res.code === 401) {
      // Indicates that the user is not authenticated
      message.info(res.msg);
      localStorage.setItem('pityToken'.null);
      localStorage.setItem('pityUser'.null);
      window.location.href = '/user/login';
    }
    message.error(res.msg);
    return false; }};Copy the code

The method to obtain the token and form headers is written here, and the code for token expiration and insufficient permission is also written for different processing.

  • Modify the SRC/services/request. Js

  • Modify request result:

  • To begin testing

    We found that our error message was not fully displayed because we needed to not validate the request in case of failure, so we also needed to adjust the back-end interface:

Check whether the value of status is True. If not, return the 110 status code

Let’s test it with a dynamic diagram:

Then test the permissions

From the application of the developer tool, you can see:

Then we adjust the permissions so that only the administrator can request the interface

Here’s a GIF:


After finishing work, recently qingming can be slightly updated a little more 😳

Good night to my twelve (including myself) viewers!

AD time

Preview address: http://47.112.32.195/

Back end code address: github.com/wuranxu/pit…

Front-end code address: github.com/wuranxu/pit…

Welcome to pay attention to the public number test development pit, there are groups together to discuss related issues ah! If the group two-dimensional code expired, you can add my personal wechat: Wuranxu I pull you into the group ~