File Coverage

blib/lib/Test/FITesque/RDF.pm
Criterion Covered Total %
statement 160 160 100.0
branch 38 38 100.0
condition 9 9 100.0
subroutine 25 25 100.0
pod 1 1 100.0
total 233 233 100.0


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