the journal module introduces a change history of resources modified by api invocations. the history of the 'customer' resource demo is accessible at /api/customers/x/journal. Change-Id: I4d5d11bc3e35160feed587ce4c1db565991866b2changes/24/1524/1
parent
bcd2de62cf
commit
96c731a144
@ -0,0 +1,621 @@
|
||||
package NGCP::Panel::Utils::Journal;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Sipwise::Base;
|
||||
use DBIx::Class::Exception;
|
||||
use NGCP::Panel::Utils::DateTime;
|
||||
use JSON;
|
||||
use HTTP::Status qw(:constants);
|
||||
use TryCatch;
|
||||
use boolean qw(true);
|
||||
use Data::HAL qw();
|
||||
use Data::HAL::Link qw();
|
||||
use Scalar::Util 'blessed';
|
||||
use Storable;
|
||||
use IO::Compress::Deflate qw($DeflateError);
|
||||
use IO::Uncompress::Inflate qw();
|
||||
use Sereal::Decoder qw();
|
||||
use Sereal::Encoder qw();
|
||||
|
||||
use constant CREATE_JOURNAL_OP => 'create';
|
||||
use constant UPDATE_JOURNAL_OP => 'update';
|
||||
use constant DELETE_JOURNAL_OP => 'delete';
|
||||
use constant _JOURNAL_OPS => { CREATE_JOURNAL_OP.'' => 1, UPDATE_JOURNAL_OP.'' => 1, DELETE_JOURNAL_OP.'' => 1 };
|
||||
|
||||
use constant OPERATION_DEFAULT_ENABLED => 0;
|
||||
|
||||
use constant API_JOURNAL_RESOURCE_NAME => 'journal';
|
||||
use constant API_JOURNALITEMTOP_RESOURCE_NAME => 'recent'; #empty to disable
|
||||
use constant JOURNAL_RESOURCE_DEFAULT_ENABLED => 0;
|
||||
|
||||
use constant CONTENT_JSON_FORMAT => 'json';
|
||||
use constant CONTENT_STORABLE_FORMAT => 'storable';
|
||||
use constant CONTENT_JSON_DEFLATE_FORMAT => 'json_deflate';
|
||||
use constant CONTENT_SEREAL_FORMAT => 'sereal';
|
||||
use constant _CONTENT_FORMATS => { CONTENT_JSON_FORMAT.'' => 1, CONTENT_STORABLE_FORMAT.'' => 1, CONTENT_JSON_DEFLATE_FORMAT.'' => 1, CONTENT_SEREAL_FORMAT.'' => 1 };
|
||||
|
||||
use constant CONTENT_DEFAULT_FORMAT => CONTENT_JSON_FORMAT;
|
||||
|
||||
use constant API_JOURNAL_RELATION => 'ngcp:'.API_JOURNAL_RESOURCE_NAME;
|
||||
#use constant API_JOURNALITEM_RELATION => 'ngcp:journalitem';
|
||||
|
||||
sub add_journal_item_hal {
|
||||
my ($controller,$c,$operation,@args) = @_;
|
||||
my $cfg = _get_api_journal_op_config($c,$controller->resource_name,$operation);
|
||||
if ($cfg->{operation_enabled}) {
|
||||
my $arg = $args[0];
|
||||
my @hal_from_item = ();
|
||||
my $params;
|
||||
if (ref $arg eq 'HASH') {
|
||||
$params = $arg;
|
||||
my $h = (defined $params->{hal} ? $params->{hal} : $params->{hal_from_item});
|
||||
$h //= (defined $params->{resource} ? $params->{resource} : $params->{resource_from_item});
|
||||
if (defined $h) {
|
||||
if (ref $h eq 'ARRAY') {
|
||||
@hal_from_item = @$h;
|
||||
} else { #if (not ref $h) {
|
||||
@hal_from_item = ( $h );
|
||||
}
|
||||
}
|
||||
} elsif (ref $arg eq 'ARRAY') {
|
||||
$params = {};
|
||||
@hal_from_item = @$arg;
|
||||
} else {
|
||||
$params = {};
|
||||
@hal_from_item = @args;
|
||||
}
|
||||
my $code = shift @hal_from_item;
|
||||
unshift(@hal_from_item,$c);
|
||||
unshift(@hal_from_item,$controller);
|
||||
unshift(@hal_from_item,$code);
|
||||
$params->{hal_from_item} = \@hal_from_item;
|
||||
$params->{operation} = $operation;
|
||||
$params->{resource_name} //= $controller->resource_name;
|
||||
$params->{format} //= $cfg->{format};
|
||||
|
||||
my $resource = shift @hal_from_item;
|
||||
my $hal;
|
||||
if (ref $resource eq 'CODE') {
|
||||
$hal = $resource->(@hal_from_item);
|
||||
} else {
|
||||
$hal = $resource;
|
||||
}
|
||||
if (ref $hal eq Data::HAL::) {
|
||||
$resource = $hal->resource;
|
||||
}
|
||||
my $id;
|
||||
if (ref $resource eq 'HASH') {
|
||||
$id = $resource->{id};
|
||||
} elsif ((defined blessed($resource)) && $resource->can('id')) {
|
||||
$id = $resource->id;
|
||||
}
|
||||
return _create_journal($controller,$c,$id,$resource,$params);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub get_api_journal_action_config {
|
||||
my ($path_part,$action_template,$journal_methods) = @_;
|
||||
my %journal_actions_found = map { $_ => 1 } @$journal_methods;
|
||||
if (exists $journal_actions_found{'item_base_journal'}) {
|
||||
my @result = ();
|
||||
if (exists $journal_actions_found{'journals_get'}) {
|
||||
my $action_config = Storable::dclone($action_template);
|
||||
$action_config->{Chained} = 'item_base_journal';
|
||||
$action_config->{PathPart} //= API_JOURNAL_RESOURCE_NAME;
|
||||
$action_config->{Args} = 0;
|
||||
$action_config->{Method} = 'GET';
|
||||
push(@result,$action_config,'journals_get');
|
||||
}
|
||||
if (exists $journal_actions_found{'journals_options'}) {
|
||||
my $action_config = Storable::dclone($action_template);
|
||||
$action_config->{Chained} = 'item_base_journal';
|
||||
$action_config->{PathPart} //= API_JOURNAL_RESOURCE_NAME;
|
||||
$action_config->{Args} = 0;
|
||||
$action_config->{Method} = 'OPTIONS';
|
||||
push(@result,$action_config,'journals_options');
|
||||
}
|
||||
if (exists $journal_actions_found{'journals_head'}) {
|
||||
my $action_config = Storable::dclone($action_template);
|
||||
$action_config->{Chained} = 'item_base_journal';
|
||||
$action_config->{PathPart} //= API_JOURNAL_RESOURCE_NAME;
|
||||
$action_config->{Args} = 0;
|
||||
$action_config->{Method} = 'HEAD';
|
||||
push(@result,$action_config,'journals_head');
|
||||
}
|
||||
if (exists $journal_actions_found{'journalsitem_get'}) {
|
||||
my $action_config = Storable::dclone($action_template);
|
||||
$action_config->{Chained} = 'item_base_journal';
|
||||
$action_config->{PathPart} //= API_JOURNAL_RESOURCE_NAME;
|
||||
$action_config->{Args} = 1;
|
||||
$action_config->{Method} = 'GET';
|
||||
push(@result,$action_config,'journalsitem_get');
|
||||
}
|
||||
if (exists $journal_actions_found{'journalsitem_options'}) {
|
||||
my $action_config = Storable::dclone($action_template);
|
||||
$action_config->{Chained} = 'item_base_journal';
|
||||
$action_config->{PathPart} //= API_JOURNAL_RESOURCE_NAME;
|
||||
$action_config->{Args} = 1;
|
||||
$action_config->{Method} = 'OPTIONS';
|
||||
push(@result,$action_config,'journalsitem_options');
|
||||
}
|
||||
if (exists $journal_actions_found{'journalsitem_head'}) {
|
||||
my $action_config = Storable::dclone($action_template);
|
||||
$action_config->{Chained} = 'item_base_journal';
|
||||
$action_config->{PathPart} //= API_JOURNAL_RESOURCE_NAME;
|
||||
$action_config->{Args} = 1;
|
||||
$action_config->{Method} = 'HEAD';
|
||||
push(@result,$action_config,'journalsitem_head');
|
||||
}
|
||||
if ((scalar @result) > 0) {
|
||||
push(@result,{
|
||||
Chained => '/',
|
||||
PathPart => $path_part,
|
||||
CaptureArgs => 1,
|
||||
},'item_base_journal');
|
||||
@result = reverse @result;
|
||||
return \@result;
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
sub get_api_journal_query_params {
|
||||
my ($query_params) = @_;
|
||||
my @params = (defined $query_params ? @$query_params : ());
|
||||
push(@params,{
|
||||
param => 'operation',
|
||||
description => 'Filter for journal items by a specific CRUD operation ("create", "update" or "delete")',
|
||||
query => {
|
||||
first => sub {
|
||||
my $q = shift;
|
||||
{ 'operation' => $q };
|
||||
},
|
||||
second => sub { },
|
||||
},
|
||||
});
|
||||
return ['journal_query_params',
|
||||
is => 'ro',
|
||||
isa => 'ArrayRef',
|
||||
default => sub { #[ #sub {[
|
||||
\@params
|
||||
}, #], #]},
|
||||
];
|
||||
}
|
||||
|
||||
sub handle_api_item_base_journal {
|
||||
my ($controller,$c,$id) = @_;
|
||||
$c->stash->{item_id_journal} = $id;
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub handle_api_journals_get {
|
||||
my ($controller,$c) = @_;
|
||||
my $page = $c->request->params->{page} // 1;
|
||||
my $rows = $c->request->params->{rows} // 10;
|
||||
{
|
||||
my $item_id = $c->stash->{item_id_journal};
|
||||
last unless $controller->valid_id($c, $item_id);
|
||||
|
||||
my $journals = get_journal_rs($controller,$c,$item_id);
|
||||
(my $total_count, $journals) = $controller->paginate_order_collection($c,$journals);
|
||||
|
||||
my (@embedded, @links);
|
||||
for my $journal($journals->all) {
|
||||
my $hal = hal_from_journal($controller,$c,$journal);
|
||||
$hal->_forcearray(1);
|
||||
push @embedded,$hal;
|
||||
my $link = get_journal_relation_link($journal,$item_id,$journal->id);
|
||||
$link->_forcearray(1);
|
||||
push @links,$link;
|
||||
}
|
||||
push @links,
|
||||
Data::HAL::Link->new(
|
||||
relation => 'curies',
|
||||
href => 'http://purl.org/sipwise/ngcp-api/#rel-{rel}',
|
||||
name => 'ngcp',
|
||||
templated => true,
|
||||
),
|
||||
Data::HAL::Link->new(relation => 'profile', href => 'http://purl.org/sipwise/ngcp-api/');
|
||||
|
||||
push @links, $controller->collection_nav_links($page, $rows, $total_count, $c->request->path, $c->request->query_params);
|
||||
|
||||
my $hal = Data::HAL->new(
|
||||
embedded => [@embedded],
|
||||
links => [@links],
|
||||
);
|
||||
$hal->resource({
|
||||
total_count => $total_count,
|
||||
});
|
||||
my $response = HTTP::Response->new(HTTP_OK, undef,
|
||||
HTTP::Headers->new($hal->http_headers(skip_links => 1)), $hal->as_json);
|
||||
$c->response->headers($response->headers);
|
||||
$c->response->body($response->content);
|
||||
return;
|
||||
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
sub handle_api_journalsitem_get {
|
||||
my ($controller,$c,$id) = @_;
|
||||
{
|
||||
my $item_id = $c->stash->{item_id_journal};
|
||||
last unless $controller->valid_id($c, $item_id);
|
||||
my $journal = undef;
|
||||
if (API_JOURNALITEMTOP_RESOURCE_NAME and $id eq API_JOURNALITEMTOP_RESOURCE_NAME) {
|
||||
$journal = get_top_journalitem($controller,$c,$item_id);
|
||||
} elsif ($controller->valid_id($c, $id)) {
|
||||
$journal = get_journalitem($c,$id);
|
||||
} else {
|
||||
last;
|
||||
}
|
||||
|
||||
last unless $controller->resource_exists($c, journal => $journal);
|
||||
|
||||
if ($journal->resource_id != $item_id) {
|
||||
$c->log->error("Journal item '" . $id . "' does not belong to '" . $controller->resource_name . '/' . $item_id . "'");
|
||||
$controller->error($c, HTTP_NOT_FOUND, "Entity 'journal' not found.");
|
||||
return;
|
||||
}
|
||||
|
||||
my $hal = hal_from_journal($controller,$c,$journal);
|
||||
|
||||
#my $response = HTTP::Response->new(HTTP_OK, undef, HTTP::Headers->new(
|
||||
# (map { # XXX Data::HAL must be able to generate links with multiple relations
|
||||
# s|rel="(http://purl.org/sipwise/ngcp-api/#rel-resellers)"|rel="item $1"|;
|
||||
# s/rel=self/rel="item self"/;
|
||||
# $_
|
||||
# } $hal->http_headers),
|
||||
#), $hal->as_json);
|
||||
$c->response->headers(HTTP::Headers->new($hal->http_headers));
|
||||
$c->response->body($hal->as_json);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
sub handle_api_journals_options {
|
||||
my ($controller, $c, $id) = @_;
|
||||
my @allowed_methods = ('OPTIONS');
|
||||
my %journal_actions_found = map { $_ => 1 } @{ $controller->attributed_methods('Journal') };
|
||||
if (exists $journal_actions_found{'journals_get'}) {
|
||||
push(@allowed_methods,'GET');
|
||||
if (exists $journal_actions_found{'journals_head'}) {
|
||||
push(@allowed_methods,'HEAD');
|
||||
}
|
||||
}
|
||||
$c->response->headers(HTTP::Headers->new(
|
||||
Allow => join(', ',@allowed_methods),
|
||||
));
|
||||
$c->response->content_type('application/json');
|
||||
$c->response->body(JSON::to_json({ methods => \@allowed_methods })."\n");
|
||||
return;
|
||||
}
|
||||
|
||||
sub handle_api_journalsitem_options {
|
||||
my ($controller, $c, $id) = @_;
|
||||
my @allowed_methods = ('OPTIONS');
|
||||
my %journal_actions_found = map { $_ => 1 } @{ $controller->attributed_methods('Journal') };
|
||||
if (exists $journal_actions_found{'journalsitem_get'}) {
|
||||
push(@allowed_methods,'GET');
|
||||
if (exists $journal_actions_found{'journalsitem_head'}) {
|
||||
push(@allowed_methods,'HEAD');
|
||||
}
|
||||
}
|
||||
$c->response->headers(HTTP::Headers->new(
|
||||
Allow => join(', ',@allowed_methods),
|
||||
));
|
||||
$c->response->content_type('application/json');
|
||||
$c->response->body(JSON::to_json({ methods => \@allowed_methods })."\n");
|
||||
return;
|
||||
}
|
||||
|
||||
sub _has_journal_method {
|
||||
|
||||
my ($controller, $action) = @_;
|
||||
my %journal_actions_found = map { $_ => 1 } @{ $controller->attributed_methods('Journal') };
|
||||
return exists $journal_actions_found{$action};
|
||||
|
||||
}
|
||||
|
||||
sub handle_api_journals_head {
|
||||
my ($controller, $c) = @_;
|
||||
if (_has_journal_method($controller,'journals_get')) {
|
||||
$c->forward('journals_get');
|
||||
$c->response->body(q());
|
||||
return;
|
||||
} else {
|
||||
$c->log->error("journals_get action not implemented: " . ref $controller);
|
||||
$c->response->status(HTTP_METHOD_NOT_ALLOWED);
|
||||
$c->response->body(q());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
sub handle_api_journalsitem_head {
|
||||
my ($controller, $c) = @_;
|
||||
if (_has_journal_method($controller,'journalsitem_get')) {
|
||||
$c->forward('journalsitem_get');
|
||||
$c->response->body(q());
|
||||
return;
|
||||
} else {
|
||||
$c->log->error("journalsitem_get not implemented: " . ref $controller);
|
||||
$c->response->status(HTTP_METHOD_NOT_ALLOWED);
|
||||
$c->response->body(q());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
sub get_journal_rs {
|
||||
my ($controller,$c,$resource_id,$all_columns) = @_;
|
||||
my $rs = $c->model('DB')->resultset('journals')
|
||||
->search({
|
||||
'resource_name' => $controller->resource_name,
|
||||
'resource_id' => $resource_id,
|
||||
},($all_columns ? undef : {
|
||||
columns => [qw(id operation resource_name resource_id timestamp username)],
|
||||
#alias => 'me',
|
||||
}));
|
||||
|
||||
if ($controller->can('journal_query_params')) {
|
||||
return $controller->apply_query_params($c,$controller->journal_query_params,$rs);
|
||||
}
|
||||
|
||||
return $rs;
|
||||
}
|
||||
|
||||
sub get_journalitem {
|
||||
my ($c,$id) = @_;
|
||||
my $journal = $c->model('DB')->resultset('journals')
|
||||
->find({
|
||||
'id' => $id,
|
||||
});
|
||||
|
||||
return $journal;
|
||||
}
|
||||
|
||||
sub get_top_journalitem {
|
||||
my ($controller,$c,$resource_id) = @_;
|
||||
return get_top_n_journalitems($controller,$c,$resource_id,1,1)->[0];
|
||||
}
|
||||
|
||||
sub get_top_n_journalitems {
|
||||
my ($controller,$c,$resource_id,$n,$all_columns) = @_;
|
||||
my @journals = get_journal_rs($controller,$c,$resource_id,$all_columns)->search(undef,{
|
||||
page => 1,
|
||||
rows => $n,
|
||||
order_by => {-desc => "id"},
|
||||
})->all;
|
||||
return \@journals;
|
||||
}
|
||||
|
||||
sub hal_from_journal {
|
||||
my ($controller,$c,$journal) = @_;
|
||||
|
||||
my %resource = $journal->get_inflated_columns;
|
||||
{
|
||||
if (exists $resource{content}) {
|
||||
try {
|
||||
$resource{content} = _deserialize_content($journal->content_format,$journal->content);
|
||||
} catch($e) {
|
||||
$resource{content} = undef;
|
||||
$c->log->error("Failed to de-serialize content snapshot of journal item '" . $journal->id . "': $e");
|
||||
#$controller->error($c, HTTP_INTERNAL_SERVER_ERROR, "Internal Server Error.");
|
||||
#return;
|
||||
last;
|
||||
};
|
||||
#delta stuff...
|
||||
}
|
||||
}
|
||||
|
||||
my $hal = Data::HAL->new(
|
||||
links => [
|
||||
Data::HAL::Link->new(
|
||||
relation => 'curies',
|
||||
href => 'http://purl.org/sipwise/ngcp-api/#rel-{rel}',
|
||||
name => 'ngcp',
|
||||
templated => true,
|
||||
),
|
||||
get_journal_relation_link($journal,$journal->resource_id,undef,'collection'),
|
||||
Data::HAL::Link->new(relation => 'profile', href => 'http://purl.org/sipwise/ngcp-api/'),
|
||||
get_journal_relation_link($journal,$journal->resource_id,$journal->id,'self'),
|
||||
Data::HAL::Link->new(relation => sprintf('ngcp:%s',$journal->resource_name),
|
||||
href => sprintf('/api/%s/%d', $journal->resource_name, $journal->resource_id)),
|
||||
],
|
||||
relation => API_JOURNAL_RELATION, #API_JOURNALITEM_RELATION,
|
||||
);
|
||||
|
||||
my $datetime_fmt = DateTime::Format::Strptime->new(
|
||||
pattern => '%F %T',
|
||||
);
|
||||
|
||||
$resource{timestamp} = $datetime_fmt->format_datetime($resource{timestamp});
|
||||
delete $resource{resource_name};
|
||||
delete $resource{resource_id};
|
||||
delete $resource{content_format};
|
||||
$hal->resource({%resource});
|
||||
|
||||
return $hal;
|
||||
}
|
||||
|
||||
sub get_journal_relation_link {
|
||||
|
||||
my ($resource,$item_id,$id,$relation) = @_;
|
||||
my $resource_name = undef;
|
||||
if (ref $resource eq 'HASH') {
|
||||
$resource_name = $resource->{resource_name};
|
||||
} elsif ((defined blessed($resource)) && $resource->can('resource_name')) { #both controllers and journal rows
|
||||
$resource_name = $resource->resource_name;
|
||||
} elsif (!ref $resource) {
|
||||
$resource_name = $resource;
|
||||
}
|
||||
if (defined $resource_name) {
|
||||
if (defined $id) {
|
||||
return Data::HAL::Link->new(
|
||||
relation => ($relation // API_JOURNAL_RELATION), #API_JOURNALITEM_RELATION),
|
||||
href => sprintf('/api/%s/%d/%s/%d', $resource_name, $item_id,API_JOURNAL_RESOURCE_NAME,$id),
|
||||
);
|
||||
} else {
|
||||
return Data::HAL::Link->new(
|
||||
relation => ($relation // API_JOURNAL_RELATION),
|
||||
href => sprintf('/api/%s/%d/%s/', $resource_name, $item_id,API_JOURNAL_RESOURCE_NAME),
|
||||
)
|
||||
}
|
||||
}
|
||||
return undef;
|
||||
|
||||
}
|
||||
|
||||
sub get_api_journal_op_config {
|
||||
my ($config,$resource_name,$operation) = @_;
|
||||
return _get_journal_op_config($config->{api_journal},undef,$resource_name,$operation);
|
||||
}
|
||||
|
||||
sub _get_api_journal_op_config {
|
||||
return _get_journal_op_config($_[0]->config->{api_journal},@_);
|
||||
}
|
||||
|
||||
sub _get_journal_op_config {
|
||||
my ($cfg,$c,$resource_name,$operation) = @_;
|
||||
#my $cfg = $c->config->{$section};
|
||||
my $format = CONTENT_DEFAULT_FORMAT;
|
||||
my $operation_enabled = OPERATION_DEFAULT_ENABLED;
|
||||
if (defined $cfg && exists $cfg->{$resource_name}) {
|
||||
$cfg = $cfg->{$resource_name};
|
||||
if (ref $cfg eq 'HASH') {
|
||||
if (ref $cfg->{operations} eq 'ARRAY') {
|
||||
foreach my $op (@{ $cfg->{operations} }) {
|
||||
if (exists _JOURNAL_OPS->{$op}) {
|
||||
if ($operation eq $op) {
|
||||
$operation_enabled = 1;
|
||||
last;
|
||||
}
|
||||
} else {
|
||||
$c->log->error("Invalid '$resource_name' operation to log in journals: $op") if defined $c;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (defined $cfg->{format}) {
|
||||
if (exists _CONTENT_FORMATS->{$cfg->{format}}) {
|
||||
$format = $cfg->{format};
|
||||
} else {
|
||||
$c->log->error("Invalid journal content format for resource '$resource_name': $cfg->{format}") if defined $c;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return { format => $format, operation_enabled => $operation_enabled};
|
||||
|
||||
}
|
||||
|
||||
sub get_journal_resource_config {
|
||||
my ($config,$resource_name) = @_;
|
||||
my $cfg = $config->{api_journal};
|
||||
my $journal_resource_enabled = JOURNAL_RESOURCE_DEFAULT_ENABLED;
|
||||
if (defined $cfg && exists $cfg->{$resource_name}) {
|
||||
$cfg = $cfg->{$resource_name};
|
||||
if (ref $cfg eq 'HASH') {
|
||||
if (defined $cfg->{enabled}) {
|
||||
if ($cfg->{enabled}) {
|
||||
$journal_resource_enabled = 1;
|
||||
} else {
|
||||
$journal_resource_enabled = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return { journal_resource_enabled => $journal_resource_enabled };
|
||||
|
||||
}
|
||||
|
||||
sub _serialize_content { #run this in eval only, deflate somehow inflicts a segfault in subsequent catalyst action when not consuming all args there.
|
||||
my ($format,$data) = @_;
|
||||
if (defined $format && defined $data) {
|
||||
if ($format eq CONTENT_JSON_FORMAT) {
|
||||
return JSON::to_json($data, { canonical => 1, pretty => 1, utf8 => 1 });
|
||||
} elsif ($format eq CONTENT_JSON_DEFLATE_FORMAT) {
|
||||
my $json = JSON::to_json($data, { canonical => 1, pretty => 1, utf8 => 1 });
|
||||
my $buf = '';
|
||||
IO::Compress::Deflate::deflate(\$json,\$buf) or die($DeflateError);
|
||||
return $buf;
|
||||
} elsif ($format eq CONTENT_STORABLE_FORMAT) {
|
||||
return Storable::nfreeze($data);
|
||||
} elsif ($format eq CONTENT_SEREAL_FORMAT) {
|
||||
return Sereal::Encoder::encode_sereal($data);
|
||||
}
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub _deserialize_content {
|
||||
my ($format,$serialized) = @_;
|
||||
if (defined $format && defined $serialized) {
|
||||
if ($format eq CONTENT_JSON_FORMAT) {
|
||||
return JSON::from_json($serialized);
|
||||
} elsif ($format eq CONTENT_JSON_DEFLATE_FORMAT) {
|
||||
my $buf = '';
|
||||
IO::Uncompress::Inflate::inflate(\$serialized,\$buf) or die($DeflateError);
|
||||
return JSON::from_json($buf);
|
||||
} elsif ($format eq CONTENT_STORABLE_FORMAT) {
|
||||
return Storable::thaw($serialized);
|
||||
} elsif ($format eq CONTENT_SEREAL_FORMAT) {
|
||||
return Sereal::Decoder::decode_sereal($serialized);
|
||||
}
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub _create_journal {
|
||||
my ($controller,$c,$id,$resource,$params) = @_;
|
||||
{
|
||||
my $content_format = ((defined $params->{format} and exists _CONTENT_FORMATS->{$params->{format}}) ? $params->{format} : CONTENT_DEFAULT_FORMAT);
|
||||
my $operation = ((defined $params->{operation} and exists _JOURNAL_OPS->{$params->{operation}}) ? $params->{operation} : undef);
|
||||
if (!defined $operation) {
|
||||
$c->log->error("Invalid '$params->{resource_name}' operation to log in journals: $operation");
|
||||
last;
|
||||
}
|
||||
my %journal = (
|
||||
operation => $operation,
|
||||
resource_name => $params->{resource_name},
|
||||
#resource_id => $id,
|
||||
timestamp => NGCP::Panel::Utils::DateTime::current_local->hires_epoch,
|
||||
content_format => $content_format,
|
||||
);
|
||||
if (defined $id) {
|
||||
$journal{resource_id} = $id;
|
||||
}
|
||||
if (defined $params->{user}) {
|
||||
$journal{username} = $params->{user};
|
||||
} elsif (defined $c->user) {
|
||||
if($c->user->roles eq 'admin' || $c->user->roles eq 'reseller') {
|
||||
$journal{username} = $c->user->login;
|
||||
} elsif($c->user->roles eq 'subscriber' || $c->user->roles eq 'subscriberadmin') {
|
||||
$journal{username} = $c->user->webusername . '@' . $c->user->domain->domain;
|
||||
}
|
||||
}
|
||||
try {
|
||||
$journal{content} = _serialize_content($content_format,$resource);
|
||||
#my $test = 1 / 0;
|
||||
} catch($e) {
|
||||
$c->log->error("Failed to serialize journal content snapshot of '" . $params->{resource_name} . '/' . $id . "': $e");
|
||||
#$controller->error($c, HTTP_INTERNAL_SERVER_ERROR, "Internal Server Error.");
|
||||
#return;
|
||||
last;
|
||||
};
|
||||
try {
|
||||
return $c->model('DB')->resultset('journals')->create(\%journal);
|
||||
} catch($e) {
|
||||
$c->log->error("Failed to create journal item for '" . $params->{resource_name} . '/' . $id . "': $e");
|
||||
#$controller->error($c, HTTP_INTERNAL_SERVER_ERROR, "Internal Server Error.");
|
||||
#return;
|
||||
last;
|
||||
};
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
|
||||
1;
|
@ -1 +1 @@
|
||||
PERL5LIB=/opt/Komodo-IDE-8/remote_debugging PERLDB_OPTS="RemotePort=127.0.0.1:42510" DBGP_IDEKEY="jdoe" CATALYST_DEBUG=1 DBIC_TRACE=1 DBIC_TRACE_PROFILE=console DEVEL_CONFESS_OPTIONS='objects builtin dump color source' perl -d `which plackup` -I ../data-hal/lib -I ../ngcp-schema/lib -I lib -I ../sipwise-base/lib/ ngcp_panel.psgi --listen /tmp/ngcp_panel_sock --nproc 1 -s FCGI -r
|
||||
PERL5LIB=/opt/Komodo-IDE-8/remote_debugging PERLDB_OPTS="RemotePort=127.0.0.1:9000" DBGP_IDEKEY="jdoe" CATALYST_DEBUG=1 DBIC_TRACE=1 DBIC_TRACE_PROFILE=console DEVEL_CONFESS_OPTIONS='objects builtin dump color source' perl -d `which plackup` -I ../data-hal/lib -I ../ngcp-schema/lib -I lib -I ../sipwise-base/lib/ ngcp_panel.psgi --listen /tmp/ngcp_panel_sock --nproc 1 -s FCGI -r
|
||||
|
@ -0,0 +1,843 @@
|
||||
use Sipwise::Base;
|
||||
use Net::Domain qw(hostfqdn);
|
||||
use LWP::UserAgent;
|
||||
use JSON qw();
|
||||
use Test::More;
|
||||
use Storable qw();
|
||||
|
||||
use LWP::Debug;
|
||||
|
||||
BEGIN {
|
||||
unshift(@INC,'../lib');
|
||||
}
|
||||
use NGCP::Panel::Utils::Journal qw();
|
||||
|
||||
use Config::General;
|
||||
my $catalyst_config = Config::General->new("../ngcp_panel.conf"); #take paths from configloader..
|
||||
my %config = $catalyst_config->getall();
|
||||
my $enable_journal_tests = 1;
|
||||
|
||||
my $uri = $ENV{CATALYST_SERVER} || ('https://'.hostfqdn.':4443');
|
||||
|
||||
my $valid_ssl_client_cert = $ENV{API_SSL_CLIENT_CERT} ||
|
||||
"/etc/ngcp-panel/api_ssl/NGCP-API-client-certificate.pem";
|
||||
my $valid_ssl_client_key = $ENV{API_SSL_CLIENT_KEY} ||
|
||||
$valid_ssl_client_cert;
|
||||
my $ssl_ca_cert = $ENV{API_SSL_CA_CERT} || "/etc/ngcp-panel/api_ssl/api_ca.crt";
|
||||
|
||||
my ($ua, $req, $res);
|
||||
$ua = LWP::UserAgent->new;
|
||||
|
||||
#$ua->ssl_opts(
|
||||
# SSL_cert_file => $valid_ssl_client_cert,
|
||||
# SSL_key_file => $valid_ssl_client_key,
|
||||
# SSL_ca_file => $ssl_ca_cert,
|
||||
#);
|
||||
|
||||
$ua->ssl_opts(
|
||||
verify_hostname => 0,
|
||||
);
|
||||
$ua->credentials("127.0.0.1:4443", "api_admin_http", 'administrator', 'administrator');
|
||||
#$ua->timeout(500); #useless, need to change the nginx timeout
|
||||
|
||||
my $t = time;
|
||||
my $default_reseller_id = 1;
|
||||
|
||||
my $billingprofile = test_billingprofile($t,$default_reseller_id);
|
||||
my $systemcontact = test_systemcontact($t);
|
||||
my $contract = test_contract($billingprofile,$systemcontact);
|
||||
(my $reseller,$billingprofile) = test_reseller($t,$contract);
|
||||
my $domain = test_domain($t,$reseller);
|
||||
my $customercontact = test_customercontact($t,$reseller);
|
||||
my $customer = test_customer($customercontact,$billingprofile);
|
||||
my $subscriber = test_subscriber($t,$customer,$domain);
|
||||
|
||||
done_testing;
|
||||
|
||||
sub test_billingprofile {
|
||||
my ($t,$reseller_id) = @_;
|
||||
$req = HTTP::Request->new('POST', $uri.'/api/billingprofiles/');
|
||||
$req->header('Content-Type' => 'application/json');
|
||||
$req->header('Prefer' => 'return=representation');
|
||||
$req->content(JSON::to_json({
|
||||
name => "test profile $t",
|
||||
handle => "testprofile$t",
|
||||
reseller_id => $reseller_id,
|
||||
}));
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 201, "POST test billing profile");
|
||||
my $billingprofile_uri = $uri.'/'.$res->header('Location');
|
||||
$req = HTTP::Request->new('GET', $billingprofile_uri);
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 200, "fetch POSTed billing profile");
|
||||
my $billingprofile = JSON::from_json($res->decoded_content);
|
||||
|
||||
_test_item_journal_link('billingprofiles',$billingprofile);
|
||||
_test_journal_options_head('billingprofiles',$billingprofile->{id});
|
||||
my $journals = {};
|
||||
my $journal = _test_journal_top_journalitem('billingprofiles',$billingprofile->{id},$billingprofile,'create',$journals);
|
||||
_test_journal_options_head('billingprofiles',$billingprofile->{id},$journal->{id});
|
||||
|
||||
$req = HTTP::Request->new('PUT', $billingprofile_uri);
|
||||
$req->header('Content-Type' => 'application/json');
|
||||
$req->header('Prefer' => 'return=representation');
|
||||
$req->content(JSON::to_json({
|
||||
name => "test profile $t PUT",
|
||||
handle => "testprofile$t",
|
||||
reseller_id => $reseller_id,
|
||||
}));
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 200, "PUT test billingprofile");
|
||||
$req = HTTP::Request->new('GET', $billingprofile_uri);
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 200, "fetch PUT test billingprofile");
|
||||
$billingprofile = JSON::from_json($res->decoded_content);
|
||||
|
||||
_test_item_journal_link('billingprofiles',$billingprofile);
|
||||
$journal = _test_journal_top_journalitem('billingprofiles',$billingprofile->{id},$billingprofile,'update',$journals,$journal);
|
||||
|
||||
$req = HTTP::Request->new('PATCH', $billingprofile_uri);
|
||||
$req->header('Content-Type' => 'application/json-patch+json');
|
||||
$req->header('Prefer' => 'return=representation');
|
||||
$req->content(JSON::to_json(
|
||||
[ { op => 'replace', path => '/name', value => "test profile $t PATCH" } ]
|
||||
));
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 200, "PATCH test billingprofile");
|
||||
$req = HTTP::Request->new('GET', $billingprofile_uri);
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 200, "fetch PATCHed test billingprofile");
|
||||
$billingprofile = JSON::from_json($res->decoded_content);
|
||||
|
||||
_test_item_journal_link('billingprofiles',$billingprofile);
|
||||
$journal = _test_journal_top_journalitem('billingprofiles',$billingprofile->{id},$billingprofile,'update',$journals,$journal);
|
||||
|
||||
_test_journal_collection('billingprofiles',$billingprofile->{id},$journals);
|
||||
|
||||
return $billingprofile;
|
||||
|
||||
}
|
||||
|
||||
sub test_contract {
|
||||
my ($billingprofile,$systemcontact) = @_;
|
||||
$req = HTTP::Request->new('POST', $uri.'/api/contracts/');
|
||||
$req->header('Content-Type' => 'application/json');
|
||||
$req->header('Prefer' => 'return=representation');
|
||||
$req->content(JSON::to_json({
|
||||
status => "active",
|
||||
contact_id => $systemcontact->{id},
|
||||
type => "reseller",
|
||||
billing_profile_id => $billingprofile->{id},
|
||||
}));
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 201, "POST test reseller contract");
|
||||
my $contract_uri = $uri.'/'.$res->header('Location');
|
||||
$req = HTTP::Request->new('GET', $contract_uri);
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 200, "fetch POSTed test reseller contract");
|
||||
my $contract = JSON::from_json($res->decoded_content);
|
||||
|
||||
_test_item_journal_link('contracts',$contract);
|
||||
_test_journal_options_head('contracts',$contract->{id});
|
||||
my $journals = {};
|
||||
my $journal = _test_journal_top_journalitem('contracts',$contract->{id},$contract,'create',$journals);
|
||||
_test_journal_options_head('contracts',$contract->{id},$journal->{id});
|
||||
|
||||
$req = HTTP::Request->new('PUT', $contract_uri);
|
||||
$req->header('Content-Type' => 'application/json');
|
||||
$req->header('Prefer' => 'return=representation');
|
||||
$req->content(JSON::to_json({
|
||||
status => "active",
|
||||
contact_id => $systemcontact->{id},
|
||||
type => "reseller",
|
||||
billing_profile_id => $billingprofile->{id},
|
||||
external_id => int(rand(10)),
|
||||
}));
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 200, "PUT test reseller contract");
|
||||
$req = HTTP::Request->new('GET', $contract_uri);
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 200, "fetch PUT test reseller contract");
|
||||
$contract = JSON::from_json($res->decoded_content);
|
||||
|
||||
_test_item_journal_link('contracts',$contract);
|
||||
$journal = _test_journal_top_journalitem('contracts',$contract->{id},$contract,'update',$journals,$journal);
|
||||
|
||||
$req = HTTP::Request->new('PATCH', $contract_uri);
|
||||
$req->header('Content-Type' => 'application/json-patch+json');
|
||||
$req->header('Prefer' => 'return=representation');
|
||||
$req->content(JSON::to_json(
|
||||
[ { op => 'replace', path => '/external_id', value => int(rand(10)) } ]
|
||||
));
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 200, "PATCH test reseller contract");
|
||||
$req = HTTP::Request->new('GET', $contract_uri);
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 200, "fetch PATCHed test reseller contract");
|
||||
$contract = JSON::from_json($res->decoded_content);
|
||||
|
||||
_test_item_journal_link('contracts',$contract);
|
||||
$journal = _test_journal_top_journalitem('contracts',$contract->{id},$contract,'update',$journals,$journal);
|
||||
|
||||
_test_journal_collection('contracts',$contract->{id},$journals);
|
||||
|
||||
return $contract;
|
||||
|
||||
}
|
||||
|
||||
sub test_customercontact {
|
||||
my ($t,$reseller) = @_;
|
||||
$req = HTTP::Request->new('POST', $uri.'/api/customercontacts/');
|
||||
$req->header('Content-Type' => 'application/json');
|
||||
$req->content(JSON::to_json({
|
||||
firstname => "cust_contact_".($t-1)."_first",
|
||||
lastname => "cust_contact_".($t-1)."_last",
|
||||
email => "cust_contact_".($t-1)."\@custcontact.invalid",
|
||||
reseller_id => $reseller->{id},
|
||||
}));
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 201, "POST test customercontact");
|
||||
my $customercontact_uri = $uri.'/'.$res->header('Location');
|
||||
$req = HTTP::Request->new('GET', $customercontact_uri);
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 200, "fetch POSTed test customercontact");
|
||||
my $customercontact = JSON::from_json($res->decoded_content);
|
||||
|
||||
_test_item_journal_link('customercontacts',$customercontact);
|
||||
_test_journal_options_head('customercontacts',$customercontact->{id});
|
||||
my $journals = {};
|
||||
my $journal = _test_journal_top_journalitem('customercontacts',$customercontact->{id},$customercontact,'create',$journals);
|
||||
_test_journal_options_head('customercontacts',$customercontact->{id},$journal->{id});
|
||||
|
||||
$req = HTTP::Request->new('PUT', $customercontact_uri);
|
||||
$req->header('Content-Type' => 'application/json');
|
||||
$req->header('Prefer' => 'return=representation');
|
||||
$req->content(JSON::to_json({
|
||||
firstname => "cust_contact_".($t-1)."_first_put",
|
||||
lastname => "cust_contact_".($t-1)."_last_put",
|
||||
email => "cust_contact_".($t-1)."_put\@custcontact.invalid",
|
||||
reseller_id => $reseller->{id},
|
||||
external_id => int(rand(10)),
|
||||
}));
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 200, "PUT test customercontact");
|
||||
$req = HTTP::Request->new('GET', $customercontact_uri);
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 200, "fetch PUT test customercontact");
|
||||
$customercontact = JSON::from_json($res->decoded_content);
|
||||
|
||||
_test_item_journal_link('customercontacts',$customercontact);
|
||||
$journal = _test_journal_top_journalitem('customercontacts',$customercontact->{id},$customercontact,'update',$journals,$journal);
|
||||
|
||||
$req = HTTP::Request->new('PATCH', $customercontact_uri);
|
||||
$req->header('Content-Type' => 'application/json-patch+json');
|
||||
$req->header('Prefer' => 'return=representation');
|
||||
$req->content(JSON::to_json(
|
||||
[ { op => 'replace', path => '/firstname', value => "cust_contact_".($t-1)."_first_patch" } ]
|
||||
));
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 200, "PATCH test customercontact");
|
||||
$req = HTTP::Request->new('GET', $customercontact_uri);
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 200, "fetch PATCHed test customercontact");
|
||||
$customercontact = JSON::from_json($res->decoded_content);
|
||||
|
||||
_test_item_journal_link('customercontacts',$customercontact);
|
||||
$journal = _test_journal_top_journalitem('customercontacts',$customercontact->{id},$customercontact,'update',$journals,$journal);
|
||||
|
||||
$req = HTTP::Request->new('DELETE', $customercontact_uri);
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 204, "delete POSTed test customercontact");
|
||||
#$domain = JSON::from_json($res->decoded_content);
|
||||
|
||||
$journal = _test_journal_top_journalitem('customercontacts',$customercontact->{id},$customercontact,'delete',$journals,$journal);
|
||||
|
||||
_test_journal_collection('customercontacts',$customercontact->{id},$journals);
|
||||
|
||||
$req = HTTP::Request->new('POST', $uri.'/api/customercontacts/');
|
||||
$req->header('Content-Type' => 'application/json');
|
||||
$req->content(JSON::to_json({
|
||||
firstname => "cust_contact_".$t."_first",
|
||||
lastname => "cust_contact_".$t."_last",
|
||||
email => "cust_contact_".$t."\@custcontact.invalid",
|
||||
reseller_id => $reseller->{id},
|
||||
}));
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 201, "POST another test customercontact");
|
||||
$customercontact_uri = $uri.'/'.$res->header('Location');
|
||||
$req = HTTP::Request->new('GET', $customercontact_uri);
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 200, "fetch POSTed test customercontact");
|
||||
$customercontact = JSON::from_json($res->decoded_content);
|
||||
|
||||
return $customercontact;
|
||||
|
||||
}
|
||||
|
||||
sub test_reseller {
|
||||
|
||||
my ($t,$contract) = @_;
|
||||
$req = HTTP::Request->new('POST', $uri.'/api/resellers/');
|
||||
$req->header('Content-Type' => 'application/json');
|
||||
$req->content(JSON::to_json({
|
||||
contract_id => $contract->{id},
|
||||
name => "test reseller $t",
|
||||
status => "active",
|
||||
}));
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 201, "POST test reseller");
|
||||
my $reseller_uri = $uri.'/'.$res->header('Location');
|
||||
$req = HTTP::Request->new('GET', $reseller_uri);
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 200, "fetch POSTed test reseller");
|
||||
my $reseller = JSON::from_json($res->decoded_content);
|
||||
|
||||
_test_item_journal_link('resellers',$reseller);
|
||||
_test_journal_options_head('resellers',$reseller->{id});
|
||||
my $journals = {};
|
||||
my $journal = _test_journal_top_journalitem('resellers',$reseller->{id},$reseller,'create',$journals);
|
||||
_test_journal_options_head('resellers',$reseller->{id},$journal->{id});
|
||||
|
||||
$req = HTTP::Request->new('PUT', $reseller_uri);
|
||||
$req->header('Content-Type' => 'application/json');
|
||||
$req->header('Prefer' => 'return=representation');
|
||||
$req->content(JSON::to_json({
|
||||
contract_id => $contract->{id},
|
||||
name => "test reseller $t PUT",
|
||||
status => "active",
|
||||
}));
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 200, "PUT test reseller");
|
||||
$req = HTTP::Request->new('GET', $reseller_uri);
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 200, "fetch PUT test reseller");
|
||||
$reseller = JSON::from_json($res->decoded_content);
|
||||
|
||||
_test_item_journal_link('resellers',$reseller);
|
||||
$journal = _test_journal_top_journalitem('resellers',$reseller->{id},$reseller,'update',$journals,$journal);
|
||||
|
||||
$req = HTTP::Request->new('PATCH', $reseller_uri);
|
||||
$req->header('Content-Type' => 'application/json-patch+json');
|
||||
$req->header('Prefer' => 'return=representation');
|
||||
$req->content(JSON::to_json(
|
||||
[ { op => 'replace', path => '/name', value => "test reseller $t PATCH" } ]
|
||||
));
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 200, "PATCH test reseller");
|
||||
$req = HTTP::Request->new('GET', $reseller_uri);
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 200, "fetch PATCHed test reseller");
|
||||
$reseller = JSON::from_json($res->decoded_content);
|
||||
|
||||
_test_item_journal_link('resellers',$reseller);
|
||||
$journal = _test_journal_top_journalitem('resellers',$reseller->{id},$reseller,'update',$journals,$journal);
|
||||
|
||||
#$req = HTTP::Request->new('DELETE', $reseller_uri);
|
||||
#$res = $ua->request($req);
|
||||
#is($res->code, 204, "delete POSTed test reseller");
|
||||
##$domain = JSON::from_json($res->decoded_content);
|
||||
#
|
||||
#$journal = _test_journal_top_journalitem('resellers',$reseller->{id},$reseller,'delete',$journals,$journal);
|
||||
_test_journal_collection('resellers',$reseller->{id},$journals);
|
||||
|
||||
#$req = HTTP::Request->new('POST', $uri.'/api/resellers/');
|
||||
#$req->header('Content-Type' => 'application/json');
|
||||
#$req->content(JSON::to_json({
|
||||
# contract_id => $contract->{id},
|
||||
# name => "test reseller $t 1",
|
||||
# status => "active",
|
||||
#}));
|
||||
#$res = $ua->request($req);
|
||||
#is($res->code, 201, "POST another test reseller");
|
||||
#$reseller_uri = $uri.'/'.$res->header('Location');
|
||||
#$req = HTTP::Request->new('GET', $reseller_uri);
|
||||
#$res = $ua->request($req);
|
||||
#is($res->code, 200, "fetch POSTed test reseller");
|
||||
#$reseller = JSON::from_json($res->decoded_content);
|
||||
|
||||
my $billingprofile_uri = $uri.'/api/billingprofiles/'.$contract->{billing_profile_id};
|
||||
$req = HTTP::Request->new('PATCH', $billingprofile_uri);
|
||||
$req->header('Content-Type' => 'application/json-patch+json');
|
||||
$req->header('Prefer' => 'return=representation');
|
||||
$req->content(JSON::to_json(
|
||||
[ { op => 'replace', path => '/reseller_id', value => $reseller->{id} } ]
|
||||
));
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 200, "PATCH test billingprofile");
|
||||
$req = HTTP::Request->new('GET', $billingprofile_uri);
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 200, "fetch PATCHed test billingprofile");
|
||||
my $billingprofile = JSON::from_json($res->decoded_content);
|
||||
|
||||
return ($reseller,$billingprofile);
|
||||
|
||||
}
|
||||
|
||||
sub test_systemcontact {
|
||||
my ($t) = @_;
|
||||
$req = HTTP::Request->new('POST', $uri.'/api/systemcontacts/');
|
||||
$req->header('Content-Type' => 'application/json');
|
||||
$req->content(JSON::to_json({
|
||||
firstname => "syst_contact_".($t-1)."_first",
|
||||
lastname => "syst_contact_".($t-1)."_last",
|
||||
email => "syst_contact_".($t-1)."\@systcontact.invalid",
|
||||
}));
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 201, "POST test systemcontact");
|
||||
my $systemcontact_uri = $uri.'/'.$res->header('Location');
|
||||
$req = HTTP::Request->new('GET', $systemcontact_uri);
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 200, "fetch POSTed test systemcontact");
|
||||
my $systemcontact = JSON::from_json($res->decoded_content);
|
||||
|
||||
_test_item_journal_link('systemcontacts',$systemcontact);
|
||||
_test_journal_options_head('systemcontacts',$systemcontact->{id});
|
||||
my $journals = {};
|
||||
my $journal = _test_journal_top_journalitem('systemcontacts',$systemcontact->{id},$systemcontact,'create',$journals);
|
||||
_test_journal_options_head('systemcontacts',$systemcontact->{id},$journal->{id});
|
||||
|
||||
$req = HTTP::Request->new('PUT', $systemcontact_uri);
|
||||
$req->header('Content-Type' => 'application/json');
|
||||
$req->header('Prefer' => 'return=representation');
|
||||
$req->content(JSON::to_json({
|
||||
firstname => "syst_contact_".($t-1)."_first_put",
|
||||
lastname => "syst_contact_".($t-1)."_last_put",
|
||||
email => "syst_contact_".($t-1)."_put\@systcontact.invalid",
|
||||
external_id => int(rand(10)),
|
||||
}));
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 200, "PUT test systemcontact");
|
||||
$req = HTTP::Request->new('GET', $systemcontact_uri);
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 200, "fetch PUT test systemcontact");
|
||||
$systemcontact = JSON::from_json($res->decoded_content);
|
||||
|
||||
_test_item_journal_link('systemcontacts',$systemcontact);
|
||||
$journal = _test_journal_top_journalitem('systemcontacts',$systemcontact->{id},$systemcontact,'update',$journals,$journal);
|
||||
|
||||
$req = HTTP::Request->new('PATCH', $systemcontact_uri);
|
||||
$req->header('Content-Type' => 'application/json-patch+json');
|
||||
$req->header('Prefer' => 'return=representation');
|
||||
$req->content(JSON::to_json(
|
||||
[ { op => 'replace', path => '/firstname', value => "syst_contact_".($t-1)."_first_patch" } ]
|
||||
));
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 200, "PATCH test systemcontact");
|
||||
$req = HTTP::Request->new('GET', $systemcontact_uri);
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 200, "fetch PATCHed test systemcontact");
|
||||
$systemcontact = JSON::from_json($res->decoded_content);
|
||||
|
||||
_test_item_journal_link('systemcontacts',$systemcontact);
|
||||
$journal = _test_journal_top_journalitem('systemcontacts',$systemcontact->{id},$systemcontact,'update',$journals,$journal);
|
||||
|
||||
$req = HTTP::Request->new('DELETE', $systemcontact_uri);
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 204, "delete POSTed test systemcontact");
|
||||
#$domain = JSON::from_json($res->decoded_content);
|
||||
|
||||
$journal = _test_journal_top_journalitem('systemcontacts',$systemcontact->{id},$systemcontact,'delete',$journals,$journal);
|
||||
|
||||
_test_journal_collection('systemcontacts',$systemcontact->{id},$journals);
|
||||
|
||||
$req = HTTP::Request->new('POST', $uri.'/api/systemcontacts/');
|
||||
$req->header('Content-Type' => 'application/json');
|
||||
$req->content(JSON::to_json({
|
||||
firstname => "syst_contact_".$t."_first",
|
||||
lastname => "syst_contact_".$t."_last",
|
||||
email => "syst_contact_".$t."\@systcontact.invalid",
|
||||
}));
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 201, "POST another test systemcontact");
|
||||
$systemcontact_uri = $uri.'/'.$res->header('Location');
|
||||
$req = HTTP::Request->new('GET', $systemcontact_uri);
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 200, "fetch POSTed test systemcontact");
|
||||
$systemcontact = JSON::from_json($res->decoded_content);
|
||||
|
||||
return $systemcontact;
|
||||
|
||||
}
|
||||
|
||||
sub test_domain {
|
||||
my ($t,$reseller) = @_;
|
||||
$req = HTTP::Request->new('POST', $uri.'/api/domains/');
|
||||
$req->header('Content-Type' => 'application/json');
|
||||
$req->content(JSON::to_json({
|
||||
domain => 'test' . ($t-1) . '.example.org',
|
||||
reseller_id => $reseller->{id},
|
||||
}));
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 201, "POST test domain");
|
||||
my $domain_uri = $uri.'/'.$res->header('Location');
|
||||
$req = HTTP::Request->new('GET', $domain_uri);
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 200, "fetch POSTed test domain");
|
||||
my $domain = JSON::from_json($res->decoded_content);
|
||||
|
||||
_test_item_journal_link('domains',$domain);
|
||||
_test_journal_options_head('domains',$domain->{id});
|
||||
my $journals = {};
|
||||
my $journal = _test_journal_top_journalitem('domains',$domain->{id},$domain,'create',$journals);
|
||||
_test_journal_options_head('domains',$domain->{id},$journal->{id});
|
||||
|
||||
$req = HTTP::Request->new('DELETE', $domain_uri);
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 204, "delete POSTed test domain");
|
||||
#$domain = JSON::from_json($res->decoded_content);
|
||||
|
||||
$journal = _test_journal_top_journalitem('domains',$domain->{id},$domain,'delete',$journals,$journal);
|
||||
|
||||
_test_journal_collection('domains',$domain->{id},$journals);
|
||||
|
||||
$req = HTTP::Request->new('POST', $uri.'/api/domains/');
|
||||
$req->header('Content-Type' => 'application/json');
|
||||
$req->content(JSON::to_json({
|
||||
domain => 'test' . $t . '.example.org',
|
||||
reseller_id => $reseller->{id},
|
||||
}));
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 201, "POST another test domain");
|
||||
$domain_uri = $uri.'/'.$res->header('Location');
|
||||
$req = HTTP::Request->new('GET', $domain_uri);
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 200, "fetch POSTed test domain");
|
||||
$domain = JSON::from_json($res->decoded_content);
|
||||
|
||||
return $domain;
|
||||
|
||||
}
|
||||
|
||||
sub test_customer {
|
||||
my ($customer_contact,$billing_profile) = @_;
|
||||
$req = HTTP::Request->new('POST', $uri.'/api/customers/');
|
||||
$req->header('Content-Type' => 'application/json');
|
||||
$req->content(JSON::to_json({
|
||||
status => "active",
|
||||
contact_id => $customer_contact->{id},
|
||||
type => "sipaccount",
|
||||
billing_profile_id => $billing_profile->{id},
|
||||
max_subscribers => undef,
|
||||
external_id => undef,
|
||||
}));
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 201, "POST test customer");
|
||||
my $customer_uri = $uri.'/'.$res->header('Location');
|
||||
$req = HTTP::Request->new('GET', $customer_uri);
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 200, "fetch POSTed test customer");
|
||||
my $customer = JSON::from_json($res->decoded_content);
|
||||
|
||||
_test_item_journal_link('customers',$customer);
|
||||
_test_journal_options_head('customers',$customer->{id});
|
||||
my $journals = {};
|
||||
my $journal = _test_journal_top_journalitem('customers',$customer->{id},$customer,'create',$journals);
|
||||
_test_journal_options_head('customers',$customer->{id},$journal->{id});
|
||||
|
||||
$req = HTTP::Request->new('PUT', $customer_uri);
|
||||
$req->header('Content-Type' => 'application/json');
|
||||
$req->header('Prefer' => 'return=representation');
|
||||
$req->content(JSON::to_json({
|
||||
status => "active",
|
||||
contact_id => $customer_contact->{id},
|
||||
type => "sipaccount",
|
||||
billing_profile_id => $billing_profile->{id}, #$billing_profile_id,
|
||||
max_subscribers => undef,
|
||||
external_id => int(rand(10)),
|
||||
}));
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 200, "PUT test customer");
|
||||
$req = HTTP::Request->new('GET', $customer_uri);
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 200, "fetch PUT test customer");
|
||||
$customer = JSON::from_json($res->decoded_content);
|
||||
|
||||
_test_item_journal_link('customers',$customer);
|
||||
$journal = _test_journal_top_journalitem('customers',$customer->{id},$customer,'update',$journals,$journal);
|
||||
|
||||
$req = HTTP::Request->new('PATCH', $customer_uri);
|
||||
$req->header('Content-Type' => 'application/json-patch+json');
|
||||
$req->header('Prefer' => 'return=representation');
|
||||
$req->content(JSON::to_json(
|
||||
[ { op => 'replace', path => '/status', value => 'pending' } ]
|
||||
));
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 200, "PATCH test customer");
|
||||
$req = HTTP::Request->new('GET', $customer_uri);
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 200, "fetch PATCHed test customer");
|
||||
$customer = JSON::from_json($res->decoded_content);
|
||||
|
||||
_test_item_journal_link('customers',$customer);
|
||||
$journal = _test_journal_top_journalitem('customers',$customer->{id},$customer,'update',$journals,$journal);
|
||||
|
||||
_test_journal_collection('customers',$customer->{id},$journals);
|
||||
|
||||
return $customer;
|
||||
|
||||
}
|
||||
|
||||
sub test_subscriber {
|
||||
my ($t,$customer,$domain) = @_;
|
||||
$req = HTTP::Request->new('POST', $uri.'/api/subscribers/');
|
||||
$req->header('Content-Type' => 'application/json');
|
||||
$req->content(JSON::to_json({
|
||||
domain_id => $domain->{id},
|
||||
username => 'test_customer_subscriber_'.($t-1),
|
||||
password => 'test_customer_subscriber_password',
|
||||
customer_id => $customer->{id},
|
||||
#primary_number
|
||||
#status => "active",
|
||||
#administrative
|
||||
#is_pbx_pilot
|
||||
#profile_set_id
|
||||
#profile_id
|
||||
#id
|
||||
#alias_numbers => []
|
||||
#customer_id - pbxaccount
|
||||
#admin
|
||||
#pbx_extension
|
||||
#is_pbx_group
|
||||
#pbx_group_ids => []
|
||||
#display_name
|
||||
#external_id
|
||||
#preferences
|
||||
#groups
|
||||
|
||||
#status => "active",
|
||||
#contact_id => $customer_contact->{id},
|
||||
#type => "sipaccount",
|
||||
#billing_profile_id => $billing_profile->{id},
|
||||
#max_subscribers => undef,
|
||||
#external_id => undef,
|
||||
}));
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 201, "POST test subscriber");
|
||||
my $subscriber_uri = $uri.'/'.$res->header('Location');
|
||||
$req = HTTP::Request->new('GET', $subscriber_uri);
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 200, "fetch POSTed test subscriber");
|
||||
my $subscriber = JSON::from_json($res->decoded_content);
|
||||
|
||||
_test_item_journal_link('subscribers',$subscriber);
|
||||
_test_journal_options_head('subscribers',$subscriber->{id});
|
||||
my $journals = {};
|
||||
my $journal = _test_journal_top_journalitem('subscribers',$subscriber->{id},$subscriber,'create',$journals);
|
||||
_test_journal_options_head('subscribers',$subscriber->{id},$journal->{id});
|
||||
|
||||
$req = HTTP::Request->new('PUT', $subscriber_uri);
|
||||
$req->header('Content-Type' => 'application/json');
|
||||
$req->header('Prefer' => 'return=representation');
|
||||
$req->content(JSON::to_json({
|
||||
domain_id => $domain->{id},
|
||||
username => 'test_customer_subscriber_'.($t-1),
|
||||
password => => 'test_customer_subscriber_password_PUT',
|
||||
customer_id => $customer->{id},
|
||||
}));
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 200, "PUT test subscriber");
|
||||
$req = HTTP::Request->new('GET', $subscriber_uri);
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 200, "fetch PUT test subscriber");
|
||||
$subscriber = JSON::from_json($res->decoded_content);
|
||||
|
||||
_test_item_journal_link('subscribers',$subscriber);
|
||||
$journal = _test_journal_top_journalitem('subscribers',$subscriber->{id},$subscriber,'update',$journals,$journal);
|
||||
|
||||
$req = HTTP::Request->new('PATCH', $subscriber_uri);
|
||||
$req->header('Content-Type' => 'application/json-patch+json');
|
||||
$req->header('Prefer' => 'return=representation');
|
||||
$req->content(JSON::to_json(
|
||||
[ { op => 'replace', path => '/password', value => 'test_customer_subscriber_password_PATCH', } ]
|
||||
));
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 200, "PATCH test subscriber");
|
||||
$req = HTTP::Request->new('GET', $subscriber_uri);
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 200, "fetch PATCHed test subscriber");
|
||||
$subscriber = JSON::from_json($res->decoded_content);
|
||||
|
||||
_test_item_journal_link('subscribers',$subscriber);
|
||||
$journal = _test_journal_top_journalitem('subscribers',$subscriber->{id},$subscriber,'update',$journals,$journal);
|
||||
|
||||
$req = HTTP::Request->new('DELETE', $subscriber_uri);
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 204, "delete POSTed test subscriber");
|
||||
#$domain = JSON::from_json($res->decoded_content);
|
||||
|
||||
$journal = _test_journal_top_journalitem('subscribers',$subscriber->{id},$subscriber,'delete',$journals,$journal);
|
||||
|
||||
_test_journal_collection('subscribers',$subscriber->{id},$journals);
|
||||
|
||||
$req = HTTP::Request->new('POST', $uri.'/api/subscribers/');
|
||||
$req->header('Content-Type' => 'application/json');
|
||||
$req->content(JSON::to_json({
|
||||
domain_id => $domain->{id},
|
||||
username => 'test_customer_subscriber_'.$t,
|
||||
password => => 'test_customer_subscriber_password',
|
||||
customer_id => $customer->{id},
|
||||
}));
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 201, "POST another test subscriber");
|
||||
$subscriber_uri = $uri.'/'.$res->header('Location');
|
||||
$req = HTTP::Request->new('GET', $subscriber_uri);
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 200, "fetch POSTed test subscriber");
|
||||
$subscriber = JSON::from_json($res->decoded_content);
|
||||
|
||||
return $subscriber;
|
||||
|
||||
}
|
||||
|
||||
sub _test_item_journal_link {
|
||||
my ($resource,$item) = @_;
|
||||
if (_is_journal_resource_enabled($resource)) {
|
||||
ok(exists $item->{_links}, "check existence of _links");
|
||||
ok($item->{_links}->{'ngcp:journal'}, "check existence of ngcp:journal link");
|
||||
ok($item->{_links}->{'ngcp:journal'}->{href} eq '/api/'.$resource . '/' . $item->{id} . '/journal/', "check if ngcp:journal link equals '/api/$resource/$item->{id}/journal/'");
|
||||
}
|
||||
}
|
||||
|
||||
sub _test_journal_top_journalitem {
|
||||
|
||||
my ($resource,$item_id,$content,$op,$journals,$old_journal) = @_;
|
||||
if (_is_journal_resource_enabled($resource)) {
|
||||
my $url = $uri.'/api/'.$resource . '/' . $item_id . '/journal/recent';
|
||||
if (defined $op) {
|
||||
$url .= '?operation=' . $op;
|
||||
}
|
||||
$req = HTTP::Request->new('GET',$url);
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 200, "check recent '$op' journalitem request");
|
||||
my $journal = JSON::from_json($res->decoded_content);
|
||||
ok(exists $journal->{id}, "check existence of id");
|
||||
ok(exists $journal->{operation}, "check existence of operation");
|
||||
ok(exists $journal->{username}, "check existence of username");
|
||||
ok(exists $journal->{timestamp}, "check existence of timestamp");
|
||||
ok(exists $journal->{content}, "check existence of content");
|
||||
|
||||
ok(exists $journal->{_links}, "check existence of _links");
|
||||
#ok(exists $journal->{_embedded}, "check existence of _embedded");
|
||||
ok($journal->{_links}->{self}, "check existence of self link");
|
||||
ok($journal->{_links}->{collection}, "check existence of collection link");
|
||||
ok($journal->{_links}->{'ngcp:'.$resource}, "check existence of ngcp:$resource link");
|
||||
ok($journal->{_links}->{'ngcp:'.$resource}->{href} eq '/api/'.$resource . '/' . $item_id, "check if ngcp:$resource link equals '/api/$resource/$item_id'");
|
||||
|
||||
if (defined $old_journal) {
|
||||
ok($journal->{timestamp} ge $old_journal->{timestamp},"check incremented timestamp");
|
||||
}
|
||||
if (defined $content) {
|
||||
my $original = Storable::dclone($content);
|
||||
delete $original->{_links};
|
||||
#delete $original->{_embedded};
|
||||
is_deeply($journal->{content}, $original, "check resource '/api/$resource/$item_id' content deeply");
|
||||
}
|
||||
if (defined $journals) {
|
||||
$journals->{$journal->{_links}->{self}->{href}} = $journal;
|
||||
}
|
||||
return $journal;
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub _test_journal_options_head {
|
||||
|
||||
my ($resource,$item_id,$id) = @_;
|
||||
if (_is_journal_resource_enabled($resource)) {
|
||||
my $url = $uri.'/api/'.$resource . '/' . $item_id . '/journal/';
|
||||
if (defined $id) {
|
||||
$url .= $id . '/';
|
||||
}
|
||||
$req = HTTP::Request->new('OPTIONS', $url);
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 200, "check journal options request");
|
||||
#is($res->header('Accept-Post'), "application/hal+json; profile=http://purl.org/sipwise/ngcp-api/#rel-customers", "check Accept-Post header in options response");
|
||||
my $opts = JSON::from_json($res->decoded_content);
|
||||
my @hopts = split /\s*,\s*/, $res->header('Allow');
|
||||
ok(exists $opts->{methods} && ref $opts->{methods} eq "ARRAY", "check for valid 'methods' in body");
|
||||
foreach my $opt(qw( GET HEAD OPTIONS )) {
|
||||
ok(grep(/^$opt$/, @hopts), "check for existence of '$opt' in Allow header");
|
||||
ok(grep(/^$opt$/, @{ $opts->{methods} }), "check for existence of '$opt' in body");
|
||||
}
|
||||
$req = HTTP::Request->new('HEAD', $url);
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 200, "check options request");
|
||||
}
|
||||
}
|
||||
|
||||
sub _test_journal_collection {
|
||||
my ($resource,$item_id,$journals) = @_;
|
||||
if (_is_journal_resource_enabled($resource)) {
|
||||
my $total_count = (defined $journals ? (scalar keys %$journals) : undef);
|
||||
my $nexturi = $uri.'/api/'.$resource . '/' . $item_id . '/journal/?page=1&rows=' . ((not defined $total_count or $total_count <= 2) ? 2 : $total_count - 1);
|
||||
do {
|
||||
$res = $ua->get($nexturi);
|
||||
is($res->code, 200, "fetch journal collection page");
|
||||
my $collection = JSON::from_json($res->decoded_content);
|
||||
my $selfuri = $uri . $collection->{_links}->{self}->{href};
|
||||
is($selfuri, $nexturi, "check _links.self.href of collection");
|
||||
my $colluri = URI->new($selfuri);
|
||||
|
||||
ok(defined $total_count ? ($collection->{total_count} == $total_count) : ($collection->{total_count} > 0), "check 'total_count' of collection");
|
||||
|
||||
my %q = $colluri->query_form;
|
||||
ok(exists $q{page} && exists $q{rows}, "check existence of 'page' and 'row' in 'self'");
|
||||
my $page = int($q{page});
|
||||
my $rows = int($q{rows});
|
||||
if($page == 1) {
|
||||
ok(!exists $collection->{_links}->{prev}->{href}, "check absence of 'prev' on first page");
|
||||
} else {
|
||||
ok(exists $collection->{_links}->{prev}->{href}, "check existence of 'prev'");
|
||||
}
|
||||
if(($collection->{total_count} / $rows) <= $page) {
|
||||
ok(!exists $collection->{_links}->{next}->{href}, "check absence of 'next' on last page");
|
||||
} else {
|
||||
ok(exists $collection->{_links}->{next}->{href}, "check existence of 'next'");
|
||||
}
|
||||
|
||||
if($collection->{_links}->{next}->{href}) {
|
||||
$nexturi = $uri . $collection->{_links}->{next}->{href};
|
||||
} else {
|
||||
$nexturi = undef;
|
||||
}
|
||||
|
||||
# TODO: I'd expect that to be an array ref in any case!
|
||||
ok(ref $collection->{_links}->{'ngcp:journal'} eq "ARRAY", "check if 'ngcp:journal' is array");
|
||||
|
||||
my $page_journals = {};
|
||||
|
||||
foreach my $journal (@{ $collection->{_links}->{'ngcp:journal'} }) {
|
||||
#delete $customers{$c->{href}};
|
||||
ok(exists $journals->{$journal->{href}},"check page journal item link");
|
||||
|
||||
$req = HTTP::Request->new('GET',$uri . $journal->{href});
|
||||
$res = $ua->request($req);
|
||||
is($res->code, 200, "fetch page journal item");
|
||||
|
||||
my $original = delete $journals->{$journal->{href}};
|
||||
$page_journals->{$original->{id}} = $original;
|
||||
}
|
||||
foreach my $journal (@{ $collection->{_embedded}->{'ngcp:journal'} }) {
|
||||
ok(exists $page_journals->{$journal->{id}},"check existence of linked journal item among embedded");
|
||||
my $original = delete $page_journals->{$journal->{id}};
|
||||
delete $original->{content};
|
||||
is_deeply($original,$journal,"compare created and embedded journal item deeply");
|
||||
}
|
||||
ok((scalar keys $page_journals) == 0,"check if all embedded journal items are linked");
|
||||
|
||||
} while($nexturi);
|
||||
|
||||
ok((scalar keys $journals) == 0,"check if journal collection lists all created journal items" . (defined $total_count ? " ($total_count)" : ''));
|
||||
}
|
||||
}
|
||||
|
||||
sub _is_journal_resource_enabled {
|
||||
my ($resource) = @_;
|
||||
my $cfg = NGCP::Panel::Utils::Journal::get_journal_resource_config(\%config,$resource);
|
||||
if (not $cfg->{journal_resource_enabled}) {
|
||||
diag("'api/$resource' journal disabled, skipping tests");
|
||||
}
|
||||
return ($enable_journal_tests && $cfg->{journal_resource_enabled});
|
||||
}
|
||||
|
||||
# vim: set tabstop=4 expandtab:
|
Loading…
Reference in new issue