Child pages
  • Туториал по написанию теста API
Skip to end of metadata
Go to start of metadata

С 1.22.00.

Эта страница документации не завершена.

Старт

Для работы API тестов нужно заполнить 2 конфигурационные переменные:

  • $conf{API_TEST_USER_LOGIN} — логин пользователя для тестов
  • $conf{API_TEST_USER_PASSWORD} — пароль пользователя для тестов

Опционально можете заполнить $conf{API_TEST_URL} - URL для тестов. По умолчанию это значение localhost.

Модуль

За пример взят модуль Portal.
Расположение Abills/modules/Portal

Создаём:

  • папку t
  • папку t/schemas
  • папку t/schemas/admin
  • папку t/schemas/user
  • файл t/Api.t

Создаём папки для каждого эндпоинта, в ADMIN или(и) USER API например:

  • articles_list        - GET /portal/articles
  • articles_info       - GET /portal/articles/:id/
  • article_add         - POST /portal/articles
  • articles_update - PUT /portal/articles/:id/
  • article_delete   - DELETE /portal/articles/:id/

Чётких правил по названию папки нет, но рекомендуем использовать конструкцию subpath_action

Ядро

За пример взят микромодуль Companies.

Расположение - t/Api

Создаём:

  • папку Companies
  • папку Companies/schemas
  • папку Companies/schemas/admin
  • файл Companies/Api.t

Создаём папки для каждого эндпоинта, в ADMIN или(и) USER API например:

  • companies                 - GET /companies
  • company                     - GET /companies/:id/
  • company_add           - POST /companies
  • companies_change - PUT /companies/:id/
  • company_delete      - DELETE /companies/:id/

Чётких правил по названию папки нет, но рекомендуем использовать конструкцию subpath_action

Написание Api.t

На данный момент этот раздел документации не завершён.

Спецификация Api.t ещё полностью не определена.
Следите за обновлениями.

Portal/t/Api.t
=head1 NAME

  Portal API test

=cut

use strict;
use warnings;

use lib '../';
use Test::More;

use FindBin '$Bin';
use FindBin qw($RealBin);
use JSON;

# Подключаем конфиг
require $Bin . '/../../../../libexec/config.pl';

# Добавляем пути компилятора
BEGIN {
  our $libpath = '../../../../';
  my $sql_type = 'mysql';
  unshift(@INC, $libpath . "Abills/$sql_type/");
  unshift(@INC, $libpath);
  unshift(@INC, $libpath . 'lib/');
  unshift(@INC, $libpath . 'libexec/');
  unshift(@INC, $libpath . 'Abills/');
  unshift(@INC, $libpath . 'Abills/modules/');
}

use Abills::Defs;
use Abills::Api::Tests::Init qw(test_runner folder_list help);
use Abills::Base qw(parse_arguments);

use Admins;
use Users;
use Portal;

our (
  %conf
);

# Обязательно подключение к базе
my $db = Abills::SQL->connect(
  $conf{dbtype}, $conf{dbhost}, $conf{dbname}, $conf{dbuser}, $conf{dbpasswd},
  {
    CHARSET => ($conf{dbcharset}) ? $conf{dbcharset} : undef,
    dbdebug => $conf{dbdebug}
  }
);
my $admin = Admins->new($db, \%conf);
my $Users = Users->new($db, $admin, \%conf);
my $Portal = Portal->new($db, $admin, \%conf);

# Выбираем последнего пользователя к нашим кредам
my $user = $Users->list({
  LOGIN      => $conf{API_TEST_USER_LOGIN} || 'test',
  COLS_NAME  => 1,
  COLS_UPPER => 1
})->[0];

# Выбираем с базы articles
my $articles = $Portal->portal_articles_list({
  COLS_NAME => 1
});

# Выбираем с базы menus
my $menus = $Portal->portal_menu_list({
  ID        => '_SHOW',
  COLS_NAME => 1
});

# Выбираем с базы newsletters
my $newsletters = $Portal->portal_newsletter_list({
  COLS_NAME => 1
});

# Выбираем с базы attachments
my $attachments = $Portal->attachment_list({
  ID        => '_SHOW',
  COLS_NAME => 1
});

# Парсим аргументы с stdin
my $ARGS = parse_arguments(\@ARGV);
# ADMIN API key
my $apiKey = $ARGS->{KEY} || $ARGV[$#ARGV] || q{};

# Получаем список request, schema.json
my @test_list = folder_list($ARGS, $RealBin);
my $debug = $ARGS->{DEBUG} || 0;

# Определяем ли это help, и принтим его в случае
if (($ARGV[0] && lc($ARGV[0]) eq 'help') || defined($ARGS->{help}) || defined($ARGS->{HELP})) {
  help();
  exit 0;
}

# Берём динамические айдишки для мока.
# Тоесть, база перед этим должна быть заполненная данными.
my $article_id = $articles->[-1]->{id} || 0;
my $newsletter_id = $newsletters->[-1]->{id} || 0;
my $attachment_id = $attachments->[-1]->{id} || 0;
my $menu_id = $menus->[-1]->{id} || 0;

# Проходимся по списку, и заменяем path params на реальные данные.
foreach my $test (@test_list) {
  if ($test->{path} =~ /portal\/articles\/:id/g) {
    $test->{path} =~ s/:id/$article_id/g;
  }
  elsif ($test->{path} =~ /portal\/newsletter\/:id/g) {
    $test->{path} =~ s/:id/$newsletter_id/g;
  }
  elsif ($test->{path} =~ /portal\/menus\/:id/g) {
    $test->{path} =~ s/:id/$menu_id/g;
  }
  elsif ($test->{path} =~ /portal\/attachment\/:id/g) {
    $test->{path} =~ s/:id/$attachment_id/g;
  }
  elsif ($test->{path} =~ /user\/portal\/news\/:id/g) {
    $test->{path} =~ s/:id/$article_id/
  }
}

# Обязательно: включаем тесты по списку
test_runner({
  apiKey => $apiKey,
  debug  => $debug,
  args   => $ARGS
}, \@test_list);

# Завершаем тест с накопленными результатами 
done_testing();

1;


Создание реквеста и json-schema

И в каждом эндпоинте создаём файлы:

  • request.json - JSON интерпретация заданного реквеста
  • schema.json - JSON-SCHEMA ожидаемого ответа

Про json-schema почитать тут.

Create

Portal/t/schemas/admin/articles_add/request.json
{
   "body": {
      "addressFlat": "4",
      "archive": 1,
      "buildId": 8,
      "content": "<p>Новая Open Source версия биллинга!</p>\n\n\n\n<p>Полный список новинок, исправлений и улучшений биллинга к новому релизу!</p>",
      "date": "2023-01-27",
      "districtId": 8,
      "domainId": 0,
      "endDate": "",
      "gid": 0,
      "importance": 0,
      "name": "Releases",
      "onMainPage": 0,
      "permalink": "releases-abills-095-blackout",
      "picture": "https://demo.abills.net.ua:9443/images/attach/portal/13863233.jpg",
      "portalMenuId": 8,
      "shortDescription": "Встречайте новый релиз 2023",
      "stName": "",
      "status": 1,
      "streetId": 0,
      "tagName": "",
      "tags": 0,
      "title": "Релиз ABillS 0.95 Blackout"
   },
   "method": "POST",
   "name": "ADMIN_PORTAL_ARTICLES_ADD",
   "path": "portal/articles/"
}

Portal/t/schemas/admin/articles_add/schema.json
{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "type": "object",
  "properties": {
    "affected": {
      "type": "integer"
    },
    "insertId": {
      "type": "integer"
    },
    "total": {
      "type": "integer"
    }
  },
  "required": ["affected", "insertId", "total"]
}

Read

Portal/t/schemas/admin/articles_list/request.json
{
   "method": "GET",
   "name": "ADMIN_PORTAL_ARTICLES_LIST",
   "path": "portal/articles/"
}
 
Portal/t/schemas/admin/articles_list/schema.json
{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "type": "object",
  "properties": {
    "list": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "archive": {
            "type": "integer"
          },
          "addressFlat": {
            "type": "string"
          },
          "buildId": {
            "type": "number"
          },
          "content": {
            "type": "string"
          },
          "date": {
            "type": "string"
          },
          "deeplink": {
            "type": "integer"
          },
          "districtId": {
            "type": "string"
          },
          "domainId": {
            "type": "integer"
          },
          "endDate": {
            "type": "string"
          },
          "etimestamp": {
            "type": "integer"
          },
          "gid": {
            "type": "integer"
          },
          "importance": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "onMainPage": {
            "type": "integer"
          },
          "permalink": {
            "type": "string"
          },
          "picture": {
            "type": "string"
          },
          "portalMenuId": {
            "type": "integer"
          },
          "shortDescription": {
            "type": "string"
          },
          "stName": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "streetId": {
            "type": "integer"
          },
          "tagName": {
            "type": "string"
          },
          "tags": {
            "type": "integer"
          },
          "title": {
            "type": "string"
          },
          "url": {
            "type": "string"
          },
          "utimestamp": {
            "type": "integer"
          }
        },
        "required": [
          "id",
          "archive",
          "addressFlat",
          "buildId",
          "content",
          "date",
          "deeplink",
          "districtId",
          "domainId",
          "endDate",
          "etimestamp",
          "gid",
          "importance",
          "name",
          "onMainPage",
          "permalink",
          "picture",
          "portalMenuId",
          "shortDescription",
          "stName",
          "status",
          "streetId",
          "tagName",
          "tags",
          "title",
          "url",
          "utimestamp"
        ]
      }
    },
    "total": {
      "type": "number"
    }
  },
  "required": [
    "list",
    "total"
  ]
}


Update

Portal/t/schemas/admin/article_update/request.json
{
   "body": {
      "addressFlat": "4",
      "archive": 1,
      "buildId": 8,
      "content": "<p>Новая Open Source версия биллинга!</p>\n\n\n\n<p>Полный список новинок, исправлений и улучшений биллинга к новому релизу!</p>",
      "date": "2023-01-27",
      "districtId": 8,
      "domainId": 0,
      "endDate": "",
      "gid": 0,
      "importance": 0,
      "name": "Releases",
      "onMainPage": 0,
      "permalink": "releases-abills-095-blackout",
      "picture": "https://demo.abills.net.ua:9443/images/attach/portal/13863233.jpg",
      "portalMenuId": 8,
      "shortDescription": "Встречайте новый релиз 2023",
      "stName": "",
      "status": 1,
      "streetId": 0,
      "tagName": "",
      "tags": 0,
      "title": "Релиз ABillS 0.95 Blackout"
   },
   "method": "PUT",
   "name": "ADMIN_PORTAL_ARTICLE_UPDATE",
   "path": "portal/articles/:id/"
}
  
Portal/t/schemas/admin/articles_update/schema.json
{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "type": "object",
  "properties": {
    "affected": {
      "type": "integer"
    },
    "total": {
      "type": "integer"
    }
  },
  "required": ["affected", "total"]
}

Delete

Portal/t/schemas/admin/article_delete/request.json
{
   "method": "DELETE",
   "name": "ADMIN_PORTAL_ARTICLE_DELETE",
   "path": "portal/articles/:id/"
}
  
Portal/t/schemas/admin/article_delete/schema.json
{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "type": "object",
  "properties": {
    "result": {
      "type": "string"
    }
  },
  "required": [
    "result"
  ]
}

Запуск теста

Для запуска теста на USER API - запускайте созданный вами Api.t, будет взяты данные автоматически с конфига.
Для запуска теста на ADMIN API - запускайте с параметром KEY=*ваш API_KEY*

Практики

  1. Чтобы не писать request.json руками - используйте ChatGPT.
    Отправляйте ему OpenAPI вашего пути, и он сгенерирует нужный вам schema.json.
    Это сэкономит вам время, и позволит избежать человеческого фактора ошибки.
  • No labels