| line | stmt | bran | cond | sub | pod | time | code | 
| 1 | 13 |  |  | 13 |  | 1148788 | use 5.014; | 
|  | 13 |  |  |  |  | 93 |  | 
| 2 | 13 |  |  | 13 |  | 68 | use strict; | 
|  | 13 |  |  |  |  | 29 |  | 
|  | 13 |  |  |  |  | 273 |  | 
| 3 | 13 |  |  | 13 |  | 61 | use warnings; | 
|  | 13 |  |  |  |  | 33 |  | 
|  | 13 |  |  |  |  | 802 |  | 
| 4 |  |  |  |  |  |  |  | 
| 5 |  |  |  |  |  |  | package Test::FITesque::RDF; | 
| 6 |  |  |  |  |  |  |  | 
| 7 |  |  |  |  |  |  | our $AUTHORITY = 'cpan:KJETILK'; | 
| 8 |  |  |  |  |  |  | our $VERSION   = '0.017_01'; | 
| 9 |  |  |  |  |  |  |  | 
| 10 | 13 |  |  | 13 |  | 6681 | use Moo; | 
|  | 13 |  |  |  |  | 124639 |  | 
|  | 13 |  |  |  |  | 65 |  | 
| 11 | 13 |  |  | 13 |  | 23718 | use Attean::RDF; | 
|  | 13 |  |  |  |  | 23704297 |  | 
|  | 13 |  |  |  |  | 148 |  | 
| 12 | 13 |  |  | 13 |  | 12602 | use Path::Tiny; | 
|  | 13 |  |  |  |  | 31 |  | 
|  | 13 |  |  |  |  | 642 |  | 
| 13 | 13 |  |  | 13 |  | 87 | use URI::NamespaceMap; | 
|  | 13 |  |  |  |  | 30 |  | 
|  | 13 |  |  |  |  | 399 |  | 
| 14 | 13 |  |  | 13 |  | 5372 | use Test::FITesque::Test; | 
|  | 13 |  |  |  |  | 10576 |  | 
|  | 13 |  |  |  |  | 435 |  | 
| 15 | 13 |  |  | 13 |  | 104 | use Types::Standard qw(InstanceOf); | 
|  | 13 |  |  |  |  | 55 |  | 
|  | 13 |  |  |  |  | 105 |  | 
| 16 | 13 |  |  | 13 |  | 6834 | use Types::Namespace qw(Iri Namespace); | 
|  | 13 |  |  |  |  | 36 |  | 
|  | 13 |  |  |  |  | 170 |  | 
| 17 | 13 |  |  | 13 |  | 6347 | use Types::Path::Tiny qw(Path); | 
|  | 13 |  |  |  |  | 35 |  | 
|  | 13 |  |  |  |  | 175 |  | 
| 18 | 13 |  |  | 13 |  | 10973 | use Types::Attean qw(to_AtteanIRI); | 
|  | 13 |  |  |  |  | 48706 |  | 
|  | 13 |  |  |  |  | 146 |  | 
| 19 | 13 |  |  | 13 |  | 3877 | use Types::URI qw(to_Uri); | 
|  | 13 |  |  |  |  | 117 |  | 
|  | 13 |  |  |  |  | 91 |  | 
| 20 | 13 |  |  | 13 |  | 3854 | use Carp qw(carp croak); | 
|  | 13 |  |  |  |  | 27 |  | 
|  | 13 |  |  |  |  | 646 |  | 
| 21 | 13 |  |  | 13 |  | 83 | use Data::Dumper; | 
|  | 13 |  |  |  |  | 27 |  | 
|  | 13 |  |  |  |  | 529 |  | 
| 22 | 13 |  |  | 13 |  | 77 | use HTTP::Request; | 
|  | 13 |  |  |  |  | 33 |  | 
|  | 13 |  |  |  |  | 453 |  | 
| 23 | 13 |  |  | 13 |  | 73 | use HTTP::Response; | 
|  | 13 |  |  |  |  | 28 |  | 
|  | 13 |  |  |  |  | 294 |  | 
| 24 | 13 |  |  | 13 |  | 67 | use LWP::UserAgent; | 
|  | 13 |  |  |  |  | 27 |  | 
|  | 13 |  |  |  |  | 343 |  | 
| 25 | 13 |  |  | 13 |  | 68 | use Try::Tiny; | 
|  | 13 |  |  |  |  | 27 |  | 
|  | 13 |  |  |  |  | 680 |  | 
| 26 | 13 |  |  | 13 |  | 7096 | use Attean::SimpleQueryEvaluator; | 
|  | 13 |  |  |  |  | 373898 |  | 
|  | 13 |  |  |  |  | 21109 |  | 
| 27 |  |  |  |  |  |  |  | 
| 28 |  |  |  |  |  |  | has source => ( | 
| 29 |  |  |  |  |  |  | is      => 'ro', | 
| 30 |  |  |  |  |  |  | isa     => Path, # TODO: Generalize to URLs | 
| 31 |  |  |  |  |  |  | required => 1, | 
| 32 |  |  |  |  |  |  | coerce  => 1, | 
| 33 |  |  |  |  |  |  | ); | 
| 34 |  |  |  |  |  |  |  | 
| 35 |  |  |  |  |  |  |  | 
| 36 |  |  |  |  |  |  | has base_uri => ( | 
| 37 |  |  |  |  |  |  | is => 'ro', | 
| 38 |  |  |  |  |  |  | isa => Iri, | 
| 39 |  |  |  |  |  |  | coerce => 1, | 
| 40 |  |  |  |  |  |  | default => sub { 'http://localhost/' } | 
| 41 |  |  |  |  |  |  | ); | 
| 42 |  |  |  |  |  |  |  | 
| 43 |  |  |  |  |  |  | has suite => ( | 
| 44 |  |  |  |  |  |  | is => 'lazy', | 
| 45 |  |  |  |  |  |  | isa => InstanceOf['Test::FITesque::Suite'], | 
| 46 |  |  |  |  |  |  | ); | 
| 47 |  |  |  |  |  |  |  | 
| 48 |  |  |  |  |  |  | sub _build_suite { | 
| 49 | 5 |  |  | 5 |  | 6137 | my $self = shift; | 
| 50 | 5 |  |  |  |  | 57 | my $suite = Test::FITesque::Suite->new(); | 
| 51 | 5 |  |  |  |  | 56 | foreach my $test (@{$self->transform_rdf}) { | 
|  | 5 |  |  |  |  | 21 |  | 
| 52 | 6 |  |  |  |  | 8918 | $suite->add(Test::FITesque::Test->new({ data => $test})); | 
| 53 |  |  |  |  |  |  | } | 
| 54 | 3 |  |  |  |  | 132 | return $suite; | 
| 55 |  |  |  |  |  |  | } | 
| 56 |  |  |  |  |  |  |  | 
| 57 |  |  |  |  |  |  |  | 
| 58 |  |  |  |  |  |  |  | 
| 59 |  |  |  |  |  |  | sub transform_rdf { | 
| 60 | 17 |  |  | 17 | 1 | 89965 | my $self = shift; | 
| 61 | 17 |  |  |  |  | 317 | my $ns = URI::NamespaceMap->new(['deps', 'dc', 'rdf']); | 
| 62 | 17 |  |  |  |  | 594651 | $ns->add_mapping(test => 'http://ontologi.es/doap-tests#'); | 
| 63 | 17 |  |  |  |  | 11826 | $ns->add_mapping(http => 'http://www.w3.org/2007/ont/http#'); | 
| 64 | 17 |  |  |  |  | 11097 | $ns->add_mapping(httph => 'http://www.w3.org/2007/ont/httph#'); | 
| 65 | 17 |  |  |  |  | 11001 | $ns->add_mapping(dqm => 'http://purl.org/dqm-vocabulary/v1/dqm#'); | 
| 66 | 17 |  |  |  |  | 10926 | $ns->add_mapping(nfo => 'http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#'); | 
| 67 | 17 |  |  |  |  | 12688 | my $parser = Attean->get_parser(filename => $self->source)->new( base => $self->base_uri ); | 
| 68 | 17 |  |  |  |  | 4509489 | my $model = Attean->temporary_model; | 
| 69 |  |  |  |  |  |  |  | 
| 70 | 17 |  |  |  |  | 591287 | my $graph_id = iri('http://example.org/graph'); # TODO: Use a proper URI for graph | 
| 71 |  |  |  |  |  |  |  | 
| 72 | 17 |  |  |  |  | 6539 | my $file_iter; | 
| 73 |  |  |  |  |  |  | try { | 
| 74 | 17 |  |  | 17 |  | 1322 | $file_iter = $parser->parse_iter_from_io( $self->source->openr_utf8 ); | 
| 75 |  |  |  |  |  |  | } catch { | 
| 76 | 1 |  |  | 1 |  | 9985 | croak 'Failed to parse ' . $self->source . " due to $_"; | 
| 77 | 17 |  |  |  |  | 305 | }; | 
| 78 | 16 |  |  |  |  | 1849047 | $model->add_iter($file_iter->as_quads($graph_id)); | 
| 79 |  |  |  |  |  |  |  | 
| 80 | 16 |  |  |  |  | 1125068 | my $tests_uri_iter = $model->objects(undef, to_AtteanIRI($ns->test->fixtures))->materialize; | 
| 81 | 16 | 100 |  |  |  | 160729 | if (scalar $tests_uri_iter->elements == 0) { | 
| 82 | 1 |  |  |  |  | 114 | croak "No tests found in " . $self->source; | 
| 83 |  |  |  |  |  |  | } | 
| 84 |  |  |  |  |  |  |  | 
| 85 | 15 | 100 |  |  |  | 3055 | if ($model->holds($tests_uri_iter->peek, to_AtteanIRI($ns->rdf->first), undef, $graph_id)) { | 
| 86 |  |  |  |  |  |  | # Then, the object is a list. This supports either unordered | 
| 87 |  |  |  |  |  |  | # objects or lists, not both. This could be changed by iterating | 
| 88 |  |  |  |  |  |  | # in the below loop, but I don't see much point to it. | 
| 89 | 6 |  |  |  |  | 14419 | $tests_uri_iter = $model->get_list( $graph_id, $tests_uri_iter->peek); | 
| 90 |  |  |  |  |  |  | } | 
| 91 | 15 |  |  |  |  | 61593 | my @data; | 
| 92 |  |  |  |  |  |  |  | 
| 93 | 15 |  |  |  |  | 91 | while (my $test_uri = $tests_uri_iter->next) { | 
| 94 | 22 |  |  |  |  | 1693 | my @instance; | 
| 95 | 22 |  |  |  |  | 183 | my $params_base_term = $model->objects($test_uri, to_AtteanIRI($ns->test->param_base))->next; | 
| 96 | 22 |  |  |  |  | 67131 | my $params_base; | 
| 97 | 22 | 100 |  |  |  | 107 | if ($params_base_term) { | 
| 98 | 10 |  |  |  |  | 241 | $params_base = URI::Namespace->new($params_base_term); | 
| 99 | 10 |  |  |  |  | 1922 | $ns->guess_and_add($params_base); | 
| 100 |  |  |  |  |  |  | } | 
| 101 | 22 |  |  |  |  | 455887 | my $test_bgp = bgp(triplepattern($test_uri, to_AtteanIRI($ns->test->test_script), variable('script_class')), | 
| 102 |  |  |  |  |  |  | triplepattern(variable('script_class'), to_AtteanIRI($ns->deps->iri('test-requirement')), variable('handler')), # Because Perl doesn't support dashes in method names | 
| 103 |  |  |  |  |  |  | triplepattern(variable('script_class'), to_AtteanIRI($ns->nfo->definesFunction), variable('method')), | 
| 104 |  |  |  |  |  |  | triplepattern($test_uri, to_AtteanIRI($ns->test->purpose), variable('description')), | 
| 105 |  |  |  |  |  |  | triplepattern($test_uri, to_AtteanIRI($ns->test->params), variable('paramid'))); | 
| 106 |  |  |  |  |  |  |  | 
| 107 | 22 |  |  |  |  | 180483 | my $e = Attean::SimpleQueryEvaluator->new( model => $model, default_graph => $graph_id, ground_blanks => 1 ); | 
| 108 | 22 |  |  |  |  | 84717 | my $test_iter = $e->evaluate( $test_bgp, $graph_id); # Each row will correspond to one test | 
| 109 |  |  |  |  |  |  |  | 
| 110 | 22 |  |  |  |  | 576164 | while (my $test = $test_iter->next) { | 
| 111 | 20 |  |  |  |  | 9639 | push(@instance, [$test->value('handler')->value]); | 
| 112 | 20 |  |  |  |  | 229 | my $method = $test->value('method')->value; | 
| 113 | 20 |  |  |  |  | 195 | my $params_iter = $model->get_quads($test->value('paramid')); # Get the parameters for each test | 
| 114 | 20 |  |  |  |  | 18434 | my $params; | 
| 115 | 20 |  |  |  |  | 147 | $params->{'-special'} = {description => $test->value('description')->value}; # Description should always be present | 
| 116 | 20 |  |  |  |  | 292 | while (my $param = $params_iter->next) { | 
| 117 |  |  |  |  |  |  | # First, see if there are HTTP request-responses that can be constructed | 
| 118 | 28 |  |  |  |  | 2691 | my $pairs_head = $model->objects($param->subject, to_AtteanIRI($ns->test->steps))->next; | 
| 119 | 28 |  |  |  |  | 80999 | my @pairs; | 
| 120 |  |  |  |  |  |  |  | 
| 121 | 28 | 100 |  |  |  | 152 | if ($pairs_head) { | 
| 122 |  |  |  |  |  |  | # There exists a list of HTTP requests and responses | 
| 123 | 10 |  |  |  |  | 62 | my $steps_iter = $model->get_list($graph_id, $pairs_head); | 
| 124 | 10 |  |  |  |  | 81747 | while (my $pairs_subject = $steps_iter->next) { | 
| 125 | 15 |  |  |  |  | 1227 | my $pairs_bgp = bgp(triplepattern($pairs_subject, to_AtteanIRI($ns->test->request), variable('request')), | 
| 126 |  |  |  |  |  |  | triplepattern($pairs_subject, to_AtteanIRI($ns->test->response_assertion), variable('response_assertion'))); | 
| 127 | 15 |  |  |  |  | 28350 | my $pair_iter = $e->evaluate( $pairs_bgp, $graph_id); # Each row will correspond to one request-response pair | 
| 128 | 15 |  |  |  |  | 121067 | my $result; | 
| 129 |  |  |  |  |  |  | # Within each pair, there will be both requests and responses | 
| 130 | 15 |  |  |  |  | 166 | my $req = HTTP::Request->new; | 
| 131 | 15 |  |  |  |  | 1272 | my $res = HTTP::Response->new; | 
| 132 | 15 |  |  |  |  | 841 | my $regex_headers = {}; | 
| 133 | 15 |  |  |  |  | 61 | while (my $pair = $pair_iter->next) { | 
| 134 |  |  |  |  |  |  | # First, do requests | 
| 135 | 15 |  |  |  |  | 5405 | my $req_entry_iter = $model->get_quads($pair->value('request')); | 
| 136 | 15 |  |  |  |  | 14428 | while (my $req_data = $req_entry_iter->next) { | 
| 137 | 55 |  |  |  |  | 15615 | my $local_header = $ns->httph->local_part($req_data->predicate); | 
| 138 | 55 | 100 |  |  |  | 10563 | if ($req_data->predicate->equals($ns->http->method)) { | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
| 139 | 14 |  |  |  |  | 3653 | $req->method($req_data->object->value); | 
| 140 |  |  |  |  |  |  | } elsif ($req_data->predicate->equals($ns->http->requestURI)) { | 
| 141 | 12 |  |  |  |  | 6324 | $req->uri(to_Uri($req_data->object)); | 
| 142 |  |  |  |  |  |  | } elsif ($req_data->predicate->equals($ns->http->content)) { | 
| 143 | 7 | 100 |  |  |  | 5539 | if ($req_data->object->is_literal) { | 
|  |  | 100 |  |  |  |  |  | 
| 144 | 4 |  |  |  |  | 142 | $req->content($req_data->object->value); # TODO: might need encoding | 
| 145 |  |  |  |  |  |  | } elsif ($req_data->object->is_iri) { | 
| 146 |  |  |  |  |  |  | # If the http:content predicate points to a IRI, the framework will retrieve content from there | 
| 147 | 2 |  |  |  |  | 135 | my $ua = LWP::UserAgent->new; | 
| 148 | 2 |  |  |  |  | 642 | my $content_response = $ua->get(to_Uri($req_data->object)); | 
| 149 | 2 | 100 |  |  |  | 111652 | if ($content_response->is_success) { | 
| 150 | 1 |  |  |  |  | 28 | $req->content($content_response->decoded_content); # TODO: might need encoding | 
| 151 |  |  |  |  |  |  | } else { | 
| 152 | 1 |  |  |  |  | 34 | croak "Could not retrieve content from " . $req_data->object->as_string . " . Got " . $content_response->status_line; | 
| 153 |  |  |  |  |  |  | } | 
| 154 |  |  |  |  |  |  | } else { | 
| 155 | 1 |  |  |  |  | 57 | croak 'Unsupported object ' . $req_data->object->as_string . " in " . $self->source; | 
| 156 |  |  |  |  |  |  | } | 
| 157 |  |  |  |  |  |  | } elsif (defined($local_header)) { | 
| 158 | 8 |  |  |  |  | 6316 | $req->push_header(_find_header($local_header) => $req_data->object->value); | 
| 159 |  |  |  |  |  |  | } | 
| 160 |  |  |  |  |  |  | } | 
| 161 |  |  |  |  |  |  |  | 
| 162 |  |  |  |  |  |  | # Now, do asserted responses | 
| 163 | 13 |  |  |  |  | 6178 | my $res_entry_iter = $model->get_quads($pair->value('response_assertion')); | 
| 164 | 13 |  |  |  |  | 13230 | while (my $res_data = $res_entry_iter->next) { | 
| 165 | 40 |  |  |  |  | 9520 | my $local_header = $ns->httph->local_part($res_data->predicate); | 
| 166 | 40 | 100 |  |  |  | 7322 | if ($res_data->predicate->equals($ns->http->status)) { | 
|  |  | 100 |  |  |  |  |  | 
| 167 | 8 | 100 |  |  |  | 2229 | if ($res_data->object->datatype->equals($ns->dqm->regex)) { | 
| 168 | 1 |  |  |  |  | 290 | $regex_headers->{'status'} = 1; | 
| 169 |  |  |  |  |  |  | } | 
| 170 | 8 |  |  |  |  | 4460 | $res->code($res_data->object->value); | 
| 171 |  |  |  |  |  |  | } elsif (defined($local_header)) { | 
| 172 | 19 |  |  |  |  | 4799 | my $cleaned_header = _find_header($local_header); | 
| 173 | 19 |  |  |  |  | 178 | $res->push_header($cleaned_header => $res_data->object->value); | 
| 174 | 19 | 100 | 100 |  |  | 969 | if ($res_data->object->is_literal && $res_data->object->datatype->equals($ns->dqm->regex)) { | 
| 175 | 3 |  |  |  |  | 1022 | $regex_headers->{$cleaned_header} = 1; | 
| 176 |  |  |  |  |  |  | } | 
| 177 |  |  |  |  |  |  | } | 
| 178 |  |  |  |  |  |  | } | 
| 179 |  |  |  |  |  |  | } | 
| 180 | 13 |  |  |  |  | 3666 | $result = { 'request' => $req, | 
| 181 |  |  |  |  |  |  | 'response' => $res, | 
| 182 |  |  |  |  |  |  | 'regex-fields' => $regex_headers }; | 
| 183 |  |  |  |  |  |  |  | 
| 184 | 13 |  |  |  |  | 335 | push(@pairs, $result); | 
| 185 |  |  |  |  |  |  | } | 
| 186 | 8 |  |  |  |  | 368 | $params->{'-special'}->{'http-pairs'} = \@pairs; | 
| 187 |  |  |  |  |  |  | } | 
| 188 | 26 | 100 | 100 |  |  | 268 | if ($param->object->is_literal || $param->object->is_iri) { | 
| 189 | 19 |  |  |  |  | 573 | my $key = $param->predicate->as_string; | 
| 190 | 19 | 100 | 100 |  |  | 1795 | if (defined($params_base) && $params_base->local_part($param->predicate)) { | 
| 191 | 16 |  |  |  |  | 3121 | $key = $params_base->local_part($param->predicate) | 
| 192 |  |  |  |  |  |  | } | 
| 193 | 19 |  |  |  |  | 3069 | my $value = $param->object->value; | 
| 194 | 19 | 100 |  |  |  | 141 | if ($param->object->is_iri) { | 
| 195 | 1 |  |  |  |  | 29 | $value = to_Uri($param->object) | 
| 196 |  |  |  |  |  |  | } | 
| 197 | 19 |  |  |  |  | 824 | $params->{$key} = $value; | 
| 198 |  |  |  |  |  |  | } | 
| 199 |  |  |  |  |  |  | } | 
| 200 | 18 |  |  |  |  | 1244 | push(@instance, [$method, $params]) | 
| 201 |  |  |  |  |  |  | } | 
| 202 | 20 | 100 |  |  |  | 1100 | carp 'Test was listed as ' . $test_uri->as_string . ' but not fully described' unless scalar @instance; | 
| 203 | 20 |  |  |  |  | 1828 | push(@data, \@instance); | 
| 204 |  |  |  |  |  |  | } | 
| 205 | 13 |  |  |  |  | 1083 | return \@data; | 
| 206 |  |  |  |  |  |  | } | 
| 207 |  |  |  |  |  |  |  | 
| 208 |  |  |  |  |  |  | sub _find_header { | 
| 209 | 27 |  |  | 27 |  | 66 | my $local_header = shift; | 
| 210 | 27 |  |  |  |  | 120 | $local_header =~ s/_/-/g; # Some heuristics for creating HTTP headers | 
| 211 | 27 |  |  |  |  | 317 | $local_header =~ s/\b(\w)/\u$1/g; | 
| 212 | 27 |  |  |  |  | 189 | return $local_header; | 
| 213 |  |  |  |  |  |  | } | 
| 214 |  |  |  |  |  |  |  | 
| 215 |  |  |  |  |  |  | 1; | 
| 216 |  |  |  |  |  |  |  | 
| 217 |  |  |  |  |  |  | __END__ | 
| 218 |  |  |  |  |  |  |  | 
| 219 |  |  |  |  |  |  | =pod | 
| 220 |  |  |  |  |  |  |  | 
| 221 |  |  |  |  |  |  | =encoding utf-8 | 
| 222 |  |  |  |  |  |  |  | 
| 223 |  |  |  |  |  |  | =head1 NAME | 
| 224 |  |  |  |  |  |  |  | 
| 225 |  |  |  |  |  |  | Test::FITesque::RDF - Formulate Test::FITesque fixture tables in RDF | 
| 226 |  |  |  |  |  |  |  | 
| 227 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 228 |  |  |  |  |  |  |  | 
| 229 |  |  |  |  |  |  | my $suite = Test::FITesque::RDF->new(source => $file)->suite; | 
| 230 |  |  |  |  |  |  | $suite->run_tests; | 
| 231 |  |  |  |  |  |  |  | 
| 232 |  |  |  |  |  |  | See C<t/integration-basic.t> for a full test script example using this simplest way. | 
| 233 |  |  |  |  |  |  |  | 
| 234 |  |  |  |  |  |  | To run a single test script with several fixture tables, you can | 
| 235 |  |  |  |  |  |  | either add the tests to a suite, like this: | 
| 236 |  |  |  |  |  |  |  | 
| 237 |  |  |  |  |  |  | my @files = ('test1.ttl','test2.ttl'); | 
| 238 |  |  |  |  |  |  | my $suite = Test::FITesque::Suite->new; | 
| 239 |  |  |  |  |  |  |  | 
| 240 |  |  |  |  |  |  | foreach my $file (@files) { | 
| 241 |  |  |  |  |  |  | $suite->add(Test::FITesque::RDF->new(source => $path . $file)->suite); | 
| 242 |  |  |  |  |  |  | } | 
| 243 |  |  |  |  |  |  | $suite->run_tests; | 
| 244 |  |  |  |  |  |  |  | 
| 245 |  |  |  |  |  |  | or iterate and run the tests for each fixture table like this: | 
| 246 |  |  |  |  |  |  |  | 
| 247 |  |  |  |  |  |  | my @files = ('test1.ttl','test2.ttl'); | 
| 248 |  |  |  |  |  |  |  | 
| 249 |  |  |  |  |  |  | foreach my $file (@files) { | 
| 250 |  |  |  |  |  |  | diag("Reading tests from $path$file"); | 
| 251 |  |  |  |  |  |  | my $suite = Test::FITesque::RDF->new(source => $path . $file)->suite; | 
| 252 |  |  |  |  |  |  | $suite->run_tests; | 
| 253 |  |  |  |  |  |  | } | 
| 254 |  |  |  |  |  |  |  | 
| 255 |  |  |  |  |  |  |  | 
| 256 |  |  |  |  |  |  |  | 
| 257 |  |  |  |  |  |  |  | 
| 258 |  |  |  |  |  |  |  | 
| 259 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 260 |  |  |  |  |  |  |  | 
| 261 |  |  |  |  |  |  | This module enables the use of Resource Description Framework to | 
| 262 |  |  |  |  |  |  | describe fixture tables. It will take the filename of an RDF file and | 
| 263 |  |  |  |  |  |  | return a L<Test::FITesque::Suite> object that can be used to run | 
| 264 |  |  |  |  |  |  | tests. | 
| 265 |  |  |  |  |  |  |  | 
| 266 |  |  |  |  |  |  | The RDF serves to identify the implementation of certain fixtures, and | 
| 267 |  |  |  |  |  |  | can also supply parameters that can be used by the tests, e.g. input | 
| 268 |  |  |  |  |  |  | parameters or expectations. See L<Test::FITesque> for more on how the | 
| 269 |  |  |  |  |  |  | fixtures are implemented. | 
| 270 |  |  |  |  |  |  |  | 
| 271 |  |  |  |  |  |  | =head2 ATTRIBUTES AND METHODS | 
| 272 |  |  |  |  |  |  |  | 
| 273 |  |  |  |  |  |  | This module implements the following attributes and methods: | 
| 274 |  |  |  |  |  |  |  | 
| 275 |  |  |  |  |  |  | =over | 
| 276 |  |  |  |  |  |  |  | 
| 277 |  |  |  |  |  |  | =item C<< source >> | 
| 278 |  |  |  |  |  |  |  | 
| 279 |  |  |  |  |  |  | Required attribute to the constructor. Takes a L<Path::Tiny> object | 
| 280 |  |  |  |  |  |  | pointing to the RDF file containing the fixture tables. The value will | 
| 281 |  |  |  |  |  |  | be converted into an appropriate object, so a string can also be | 
| 282 |  |  |  |  |  |  | supplied. | 
| 283 |  |  |  |  |  |  |  | 
| 284 |  |  |  |  |  |  | =item C<< suite >> | 
| 285 |  |  |  |  |  |  |  | 
| 286 |  |  |  |  |  |  | Will return a L<Test::FITesque::Suite> object, based on the RDF data supplied to the constructor. | 
| 287 |  |  |  |  |  |  |  | 
| 288 |  |  |  |  |  |  | =item C<< transform_rdf >> | 
| 289 |  |  |  |  |  |  |  | 
| 290 |  |  |  |  |  |  | Will return an arrayref containing tests in the structure used by | 
| 291 |  |  |  |  |  |  | L<Test::FITesque::Test>. Most users will rather call the C<suite> | 
| 292 |  |  |  |  |  |  | method than to call this method directly. | 
| 293 |  |  |  |  |  |  |  | 
| 294 |  |  |  |  |  |  | =item C<< base_uri >> | 
| 295 |  |  |  |  |  |  |  | 
| 296 |  |  |  |  |  |  | A L<IRI> to use in parsing the RDF fixture tables to resolve any relative URIs. | 
| 297 |  |  |  |  |  |  |  | 
| 298 |  |  |  |  |  |  | =back | 
| 299 |  |  |  |  |  |  |  | 
| 300 |  |  |  |  |  |  | =head2 REQUIRED RDF | 
| 301 |  |  |  |  |  |  |  | 
| 302 |  |  |  |  |  |  | The following must exist in the test description (see below for an example and prefix expansions): | 
| 303 |  |  |  |  |  |  |  | 
| 304 |  |  |  |  |  |  | =over | 
| 305 |  |  |  |  |  |  |  | 
| 306 |  |  |  |  |  |  | =item C<< test:fixtures >> | 
| 307 |  |  |  |  |  |  |  | 
| 308 |  |  |  |  |  |  | The object(s) of this predicate lists the test fixtures that will run | 
| 309 |  |  |  |  |  |  | for this test suite. May take an RDF List. Links to the test | 
| 310 |  |  |  |  |  |  | descriptions, which follow below. | 
| 311 |  |  |  |  |  |  |  | 
| 312 |  |  |  |  |  |  |  | 
| 313 |  |  |  |  |  |  | =item C<< test:test_script >> | 
| 314 |  |  |  |  |  |  |  | 
| 315 |  |  |  |  |  |  | The object of this predicate points to information on how the actual | 
| 316 |  |  |  |  |  |  | test will be run. That is formulated in a separate resource which | 
| 317 |  |  |  |  |  |  | requires two predicates, C<< deps:test-requirement >> predicate, whose | 
| 318 |  |  |  |  |  |  | object contains the class name of the implementation of the tests; and | 
| 319 |  |  |  |  |  |  | C<< nfo:definesFunction >> whose object is a string which matches the | 
| 320 |  |  |  |  |  |  | actual function name within that class. | 
| 321 |  |  |  |  |  |  |  | 
| 322 |  |  |  |  |  |  | =item C<< test:purpose >> | 
| 323 |  |  |  |  |  |  |  | 
| 324 |  |  |  |  |  |  | The object of this predicate provides a literal description of the test. | 
| 325 |  |  |  |  |  |  |  | 
| 326 |  |  |  |  |  |  | =item C<< test:params >> | 
| 327 |  |  |  |  |  |  |  | 
| 328 |  |  |  |  |  |  | The object of this predicate links to the parameters, which may have | 
| 329 |  |  |  |  |  |  | many different shapes. See below for examples. | 
| 330 |  |  |  |  |  |  |  | 
| 331 |  |  |  |  |  |  | =back | 
| 332 |  |  |  |  |  |  |  | 
| 333 |  |  |  |  |  |  | =head2 PARAMETERIZATION | 
| 334 |  |  |  |  |  |  |  | 
| 335 |  |  |  |  |  |  | This module seeks to parameterize the tests, and does so using mostly | 
| 336 |  |  |  |  |  |  | the C<test:params> predicate above. This is passed on as a hashref to | 
| 337 |  |  |  |  |  |  | the test scripts. | 
| 338 |  |  |  |  |  |  |  | 
| 339 |  |  |  |  |  |  | There are two main ways currently implemented, one creates key-value | 
| 340 |  |  |  |  |  |  | pairs, and uses predicates and objects for that respectively, in | 
| 341 |  |  |  |  |  |  | vocabularies chosen by the test writer. The other main way is create | 
| 342 |  |  |  |  |  |  | lists of HTTP requests and responses. | 
| 343 |  |  |  |  |  |  |  | 
| 344 |  |  |  |  |  |  | If the object of a test parameter is a literal, it will be passed as a | 
| 345 |  |  |  |  |  |  | plain string, if it is a L<Attean::IRI>, it will be passed as a L<URI> | 
| 346 |  |  |  |  |  |  | object. | 
| 347 |  |  |  |  |  |  |  | 
| 348 |  |  |  |  |  |  | Additionally, a special parameter C<-special> is passed on for | 
| 349 |  |  |  |  |  |  | internal framework use. The leading dash is not allowed as the start | 
| 350 |  |  |  |  |  |  | character of a local name, and therefore chosen to avoid conflicts | 
| 351 |  |  |  |  |  |  | with other parameters. | 
| 352 |  |  |  |  |  |  |  | 
| 353 |  |  |  |  |  |  | The literal given in C<test:purpose> above is passed on as with the | 
| 354 |  |  |  |  |  |  | C<description> key in this hashref. | 
| 355 |  |  |  |  |  |  |  | 
| 356 |  |  |  |  |  |  | =head2 RDF EXAMPLE | 
| 357 |  |  |  |  |  |  |  | 
| 358 |  |  |  |  |  |  | The below example starts with prefix declarations. Then, the | 
| 359 |  |  |  |  |  |  | tests in the fixture table are listed explicitly. Only tests mentioned | 
| 360 |  |  |  |  |  |  | using the C<test:fixtures> predicate will be used. Tests may be an RDF | 
| 361 |  |  |  |  |  |  | List, in which case, the tests will run in the specified sequence, if | 
| 362 |  |  |  |  |  |  | not, no sequence may be assumed. | 
| 363 |  |  |  |  |  |  |  | 
| 364 |  |  |  |  |  |  | Then, two test fixtures are declared. The actual implementation is | 
| 365 |  |  |  |  |  |  | referenced through C<test:test_script> for both functions. | 
| 366 |  |  |  |  |  |  |  | 
| 367 |  |  |  |  |  |  | The C<test:params> predicate is used to link the parameters that will | 
| 368 |  |  |  |  |  |  | be sent as a hashref into the function. The <test:purpose> predicate | 
| 369 |  |  |  |  |  |  | is required to exist outside of the parameters, but will be included | 
| 370 |  |  |  |  |  |  | as a parameter as well, named C<description> in the C<-special> | 
| 371 |  |  |  |  |  |  | hashref. | 
| 372 |  |  |  |  |  |  |  | 
| 373 |  |  |  |  |  |  | There are two mechanisms for passing parameters to the test scripts, | 
| 374 |  |  |  |  |  |  | one is simply to pass arbitrary key-value pairs, the other is to pass | 
| 375 |  |  |  |  |  |  | lists of HTTP request-response objects. Both mechanisms may be used. | 
| 376 |  |  |  |  |  |  |  | 
| 377 |  |  |  |  |  |  | =head3 Key-value parameters | 
| 378 |  |  |  |  |  |  |  | 
| 379 |  |  |  |  |  |  | The key of the hashref passed as arguments will be the local part of | 
| 380 |  |  |  |  |  |  | the predicate used in the description (i.e. the part after the colon | 
| 381 |  |  |  |  |  |  | in e.g. C<my:all>). It is up to the test writer to mint the URIs of | 
| 382 |  |  |  |  |  |  | the parameters. | 
| 383 |  |  |  |  |  |  |  | 
| 384 |  |  |  |  |  |  | The test writer may optionally use a C<param_base> to indicate the | 
| 385 |  |  |  |  |  |  | namespace, in which case the the local part is resolved by the | 
| 386 |  |  |  |  |  |  | framework, using L<URI::NamespaceMap>. If C<param_base> is not given, | 
| 387 |  |  |  |  |  |  | the full URI will be passed to the test script. | 
| 388 |  |  |  |  |  |  |  | 
| 389 |  |  |  |  |  |  |  | 
| 390 |  |  |  |  |  |  | @prefix test: <http://ontologi.es/doap-tests#> . | 
| 391 |  |  |  |  |  |  | @prefix deps: <http://ontologi.es/doap-deps#>. | 
| 392 |  |  |  |  |  |  | @prefix dc:   <http://purl.org/dc/terms/> . | 
| 393 |  |  |  |  |  |  | @prefix my:   <http://example.org/my-parameters#> . | 
| 394 |  |  |  |  |  |  | @prefix nfo:  <http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#> . | 
| 395 |  |  |  |  |  |  | @prefix :     <http://example.org/test#> . | 
| 396 |  |  |  |  |  |  |  | 
| 397 |  |  |  |  |  |  |  | 
| 398 |  |  |  |  |  |  | :test_list a test:FixtureTable ; | 
| 399 |  |  |  |  |  |  | test:fixtures :test1, :test2 . | 
| 400 |  |  |  |  |  |  |  | 
| 401 |  |  |  |  |  |  | :test1 a test:AutomatedTest ; | 
| 402 |  |  |  |  |  |  | test:param_base <http://example.org/my-parameters#> ; | 
| 403 |  |  |  |  |  |  | test:purpose "Echo a string"@en ; | 
| 404 |  |  |  |  |  |  | test:test_script <http://example.org/simple#string_found> ; | 
| 405 |  |  |  |  |  |  | test:params [ my:all "counter-clockwise dahut" ] . | 
| 406 |  |  |  |  |  |  |  | 
| 407 |  |  |  |  |  |  | :test2 a test:AutomatedTest ; | 
| 408 |  |  |  |  |  |  | test:param_base <http://example.org/my-parameters#> ; | 
| 409 |  |  |  |  |  |  | test:purpose "Multiply two numbers"@en ; | 
| 410 |  |  |  |  |  |  | test:test_script <http://example.org/multi#multiplication> ; | 
| 411 |  |  |  |  |  |  | test:params [ | 
| 412 |  |  |  |  |  |  | my:factor1 6 ; | 
| 413 |  |  |  |  |  |  | my:factor2 7 ; | 
| 414 |  |  |  |  |  |  | my:product 42 | 
| 415 |  |  |  |  |  |  | ] . | 
| 416 |  |  |  |  |  |  |  | 
| 417 |  |  |  |  |  |  | <http://example.org/simple#string_found> a nfo:SoftwareItem ; | 
| 418 |  |  |  |  |  |  | nfo:definesFunction "string_found" ; | 
| 419 |  |  |  |  |  |  | deps:test-requirement "Internal::Fixture::Simple"^^deps:CpanId . | 
| 420 |  |  |  |  |  |  |  | 
| 421 |  |  |  |  |  |  | <http://example.org/multi#multiplication> a nfo:SoftwareItem ; | 
| 422 |  |  |  |  |  |  | nfo:definesFunction "multiplication" ; | 
| 423 |  |  |  |  |  |  | deps:test-requirement "Internal::Fixture::Multi"^^deps:CpanId . | 
| 424 |  |  |  |  |  |  |  | 
| 425 |  |  |  |  |  |  |  | 
| 426 |  |  |  |  |  |  |  | 
| 427 |  |  |  |  |  |  | =head3 HTTP request-response lists | 
| 428 |  |  |  |  |  |  |  | 
| 429 |  |  |  |  |  |  | To allow testing HTTP-based interfaces, this module also allows the | 
| 430 |  |  |  |  |  |  | construction of an ordered list of HTTP requests and response pairs. | 
| 431 |  |  |  |  |  |  | With those, the framework will construct L<HTTP::Request> and | 
| 432 |  |  |  |  |  |  | L<HTTP::Response> objects. In tests scripts, the request | 
| 433 |  |  |  |  |  |  | objects will typically be passed to the L<LWP::UserAgent> as input, | 
| 434 |  |  |  |  |  |  | and then the response from the remote server will be compared with the | 
| 435 |  |  |  |  |  |  | asserted L<HTTP::Response>s made by the test fixture. | 
| 436 |  |  |  |  |  |  |  | 
| 437 |  |  |  |  |  |  | We will go through an example in chunks: | 
| 438 |  |  |  |  |  |  |  | 
| 439 |  |  |  |  |  |  | @prefix test: <http://ontologi.es/doap-tests#> . | 
| 440 |  |  |  |  |  |  | @prefix deps: <http://ontologi.es/doap-deps#>. | 
| 441 |  |  |  |  |  |  | @prefix httph:<http://www.w3.org/2007/ont/httph#> . | 
| 442 |  |  |  |  |  |  | @prefix http: <http://www.w3.org/2007/ont/http#> . | 
| 443 |  |  |  |  |  |  | @prefix nfo:  <http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#> . | 
| 444 |  |  |  |  |  |  | @prefix :     <http://example.org/test#> . | 
| 445 |  |  |  |  |  |  |  | 
| 446 |  |  |  |  |  |  | :test_list a test:FixtureTable ; | 
| 447 |  |  |  |  |  |  | test:fixtures :public_writeread_unauthn_alt . | 
| 448 |  |  |  |  |  |  |  | 
| 449 |  |  |  |  |  |  | :public_writeread_unauthn_alt a test:AutomatedTest ; | 
| 450 |  |  |  |  |  |  | test:purpose "To test if we can write first using HTTP PUT then read with GET"@en ; | 
| 451 |  |  |  |  |  |  | test:test_script <http://example.org/httplist#http_req_res_list_unauthenticated> ; | 
| 452 |  |  |  |  |  |  | test:params [ | 
| 453 |  |  |  |  |  |  | test:steps ( | 
| 454 |  |  |  |  |  |  | [ | 
| 455 |  |  |  |  |  |  | test:request :public_writeread_unauthn_alt_put_req ; | 
| 456 |  |  |  |  |  |  | test:response_assertion :public_writeread_unauthn_alt_put_res | 
| 457 |  |  |  |  |  |  | ] | 
| 458 |  |  |  |  |  |  | [ | 
| 459 |  |  |  |  |  |  | test:request :public_writeread_unauthn_alt_get_req ; | 
| 460 |  |  |  |  |  |  | test:response_assertion :public_writeread_unauthn_alt_get_res | 
| 461 |  |  |  |  |  |  | ] | 
| 462 |  |  |  |  |  |  | ) | 
| 463 |  |  |  |  |  |  | ] . | 
| 464 |  |  |  |  |  |  |  | 
| 465 |  |  |  |  |  |  | <http://example.org/httplist#http_req_res_list_unauthenticated> a nfo:SoftwareItem ; | 
| 466 |  |  |  |  |  |  | deps:test-requirement "Example::Fixture::HTTPList"^^deps:CpanId ; | 
| 467 |  |  |  |  |  |  | nfo:definesFunction "http_req_res_list_unauthenticated" . | 
| 468 |  |  |  |  |  |  |  | 
| 469 |  |  |  |  |  |  |  | 
| 470 |  |  |  |  |  |  |  | 
| 471 |  |  |  |  |  |  | In the above, after the prefixes, a single test is declared using the | 
| 472 |  |  |  |  |  |  | C<test:fixtures> predicate, linking to a description of the test. The | 
| 473 |  |  |  |  |  |  | test is then described as an <test:AutomatedTest>, and it's purpose is | 
| 474 |  |  |  |  |  |  | declared. It then links to its concrete implementation, which is given | 
| 475 |  |  |  |  |  |  | in the last three triples in the above. | 
| 476 |  |  |  |  |  |  |  | 
| 477 |  |  |  |  |  |  | Then, the parameterization is started. In this example, there are two | 
| 478 |  |  |  |  |  |  | HTTP request-response pairs, which are given as a list object to the | 
| 479 |  |  |  |  |  |  | C<test:steps> predicate. | 
| 480 |  |  |  |  |  |  |  | 
| 481 |  |  |  |  |  |  | To link the request, the C<test:request> predicate is used, to link | 
| 482 |  |  |  |  |  |  | the asserted response, the C<test:response_assertion> predicate is | 
| 483 |  |  |  |  |  |  | used. | 
| 484 |  |  |  |  |  |  |  | 
| 485 |  |  |  |  |  |  | Next, we look into the actual request and response messages linked from the above: | 
| 486 |  |  |  |  |  |  |  | 
| 487 |  |  |  |  |  |  | :public_writeread_unauthn_alt_put_req a http:RequestMessage ; | 
| 488 |  |  |  |  |  |  | http:method "PUT" ; | 
| 489 |  |  |  |  |  |  | httph:content_type "text/turtle" ; | 
| 490 |  |  |  |  |  |  | http:content "</public/foobar.ttl#dahut> a <http://example.org/Cryptid> ." ; | 
| 491 |  |  |  |  |  |  | http:requestURI </public/foobar.ttl> . | 
| 492 |  |  |  |  |  |  |  | 
| 493 |  |  |  |  |  |  | :public_writeread_unauthn_alt_put_res a http:ResponseMessage ; | 
| 494 |  |  |  |  |  |  | http:status 201 . | 
| 495 |  |  |  |  |  |  |  | 
| 496 |  |  |  |  |  |  | :public_writeread_unauthn_alt_get_req a http:RequestMessage ; | 
| 497 |  |  |  |  |  |  | http:method "GET" ; | 
| 498 |  |  |  |  |  |  | http:requestURI </public/foobar.ttl> . | 
| 499 |  |  |  |  |  |  |  | 
| 500 |  |  |  |  |  |  | :public_writeread_unauthn_alt_get_res a http:ResponseMessage ; | 
| 501 |  |  |  |  |  |  | httph:accept_post  "text/turtle", "application/ld+json" ; | 
| 502 |  |  |  |  |  |  | httph:content_type "text/turtle" . | 
| 503 |  |  |  |  |  |  |  | 
| 504 |  |  |  |  |  |  | These should be self-explanatory, but note that headers are given with | 
| 505 |  |  |  |  |  |  | lower-case names and underscores. They will be transformed to headers | 
| 506 |  |  |  |  |  |  | by replacing underscores with dashes and upcase the first letters. | 
| 507 |  |  |  |  |  |  |  | 
| 508 |  |  |  |  |  |  | This module will transform the above to data structures that are | 
| 509 |  |  |  |  |  |  | suitable to be passed to L<Test::Fitesque>, and the above will appear as | 
| 510 |  |  |  |  |  |  |  | 
| 511 |  |  |  |  |  |  | { | 
| 512 |  |  |  |  |  |  | '-special' => { | 
| 513 |  |  |  |  |  |  | 'http-pairs' => [ | 
| 514 |  |  |  |  |  |  | { | 
| 515 |  |  |  |  |  |  | 'request'  => ... , | 
| 516 |  |  |  |  |  |  | 'response' => ... , | 
| 517 |  |  |  |  |  |  | }, | 
| 518 |  |  |  |  |  |  | { ... } | 
| 519 |  |  |  |  |  |  | ] | 
| 520 |  |  |  |  |  |  | }, | 
| 521 |  |  |  |  |  |  | 'description' => 'To test if we can write first using HTTP PUT then read with GET' | 
| 522 |  |  |  |  |  |  | }, | 
| 523 |  |  |  |  |  |  | } | 
| 524 |  |  |  |  |  |  |  | 
| 525 |  |  |  |  |  |  |  | 
| 526 |  |  |  |  |  |  | Note that there are more examples in this module's test suite in the | 
| 527 |  |  |  |  |  |  | C<t/data/> directory. | 
| 528 |  |  |  |  |  |  |  | 
| 529 |  |  |  |  |  |  | You may maintain client state in a test script (i.e. for one | 
| 530 |  |  |  |  |  |  | C<test:AutomatedTest>, as it is simply one script, so the result of | 
| 531 |  |  |  |  |  |  | one request may be used to influence the next. Server state can be | 
| 532 |  |  |  |  |  |  | relied on between different tests by using an C<rdf:List> of test | 
| 533 |  |  |  |  |  |  | fixtures if it writes something into the server, there is nothing in | 
| 534 |  |  |  |  |  |  | the framework that changes that. | 
| 535 |  |  |  |  |  |  |  | 
| 536 |  |  |  |  |  |  | To use data from one response to influence subsequent requests, the | 
| 537 |  |  |  |  |  |  | framework supports datatyping literals with the C<dqm:regex> datatype | 
| 538 |  |  |  |  |  |  | for headers and HTTP status codes, for example: | 
| 539 |  |  |  |  |  |  |  | 
| 540 |  |  |  |  |  |  | :check_acl_location_res a http:ResponseMessage ; | 
| 541 |  |  |  |  |  |  | httph:link '<(.*?)>;\\s+rel="acl"'^^dqm:regex ; | 
| 542 |  |  |  |  |  |  | http:status "200|204"^^dqm:regex . | 
| 543 |  |  |  |  |  |  |  | 
| 544 |  |  |  |  |  |  | This makes it possible to use a Perl regular expression, which can be | 
| 545 |  |  |  |  |  |  | executed in a test script if desired. If present, it will supply | 
| 546 |  |  |  |  |  |  | another hashref to the C<http-pairs> key with the key C<regex-fields> | 
| 547 |  |  |  |  |  |  | containing hashrefs with the header field that had a correspondiing | 
| 548 |  |  |  |  |  |  | object datatyped regex as key and simply C<1> as value. | 
| 549 |  |  |  |  |  |  |  | 
| 550 |  |  |  |  |  |  | =head1 TODO | 
| 551 |  |  |  |  |  |  |  | 
| 552 |  |  |  |  |  |  | Separate the implementation-specific details (such as C<deps:test-requirement>) | 
| 553 |  |  |  |  |  |  | from the actual fixture tables. | 
| 554 |  |  |  |  |  |  |  | 
| 555 |  |  |  |  |  |  | =head1 BUGS | 
| 556 |  |  |  |  |  |  |  | 
| 557 |  |  |  |  |  |  | Please report any bugs to | 
| 558 |  |  |  |  |  |  | L<https://github.com/kjetilk/p5-test-fitesque-rdf/issues>. | 
| 559 |  |  |  |  |  |  |  | 
| 560 |  |  |  |  |  |  | =head1 SEE ALSO | 
| 561 |  |  |  |  |  |  |  | 
| 562 |  |  |  |  |  |  | =head1 AUTHOR | 
| 563 |  |  |  |  |  |  |  | 
| 564 |  |  |  |  |  |  | Kjetil Kjernsmo E<lt>kjetilk@cpan.orgE<gt>. | 
| 565 |  |  |  |  |  |  |  | 
| 566 |  |  |  |  |  |  | =head1 COPYRIGHT AND LICENCE | 
| 567 |  |  |  |  |  |  |  | 
| 568 |  |  |  |  |  |  | This software is Copyright (c) 2019 by Inrupt Inc. | 
| 569 |  |  |  |  |  |  |  | 
| 570 |  |  |  |  |  |  | This is free software, licensed under: | 
| 571 |  |  |  |  |  |  |  | 
| 572 |  |  |  |  |  |  | The MIT (X11) License | 
| 573 |  |  |  |  |  |  |  | 
| 574 |  |  |  |  |  |  |  | 
| 575 |  |  |  |  |  |  | =head1 DISCLAIMER OF WARRANTIES | 
| 576 |  |  |  |  |  |  |  | 
| 577 |  |  |  |  |  |  | THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED | 
| 578 |  |  |  |  |  |  | WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF | 
| 579 |  |  |  |  |  |  | MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. | 
| 580 |  |  |  |  |  |  |  |