--- #check options - name: check OPTIONS for contracts type: item method: OPTIONS path: /api/contracts/ conditions: is: code: 200 header: Accept-Post: application/hal+json; profile=http://purl.org/sipwise/ngcp-api/#rel-contracts ok: options: - GET - HEAD - OPTIONS - POST #create a BillingProfile - name: create a BillingProfile type: item thread: 1 method: POST path: /api/billingprofiles/ header: Content-Type: application/json Prefer: return=representation content: name: test profile ${unique_id} handle: test_profile_handle${unique_id} reseller_id: 1 conditions: is: code: 201 retain: billing_profile_id: header.location #create a Customer Contact - name: create a Customer Contact type: item method: POST path: /api/customercontacts/ header: Content-Type: application/json content: firstname: cust_contact_first lastname: cust_contact_last email: cust_contact@custcontact.invalid reseller_id: 1 conditions: is: code: 201 retain: customer_contact_path: header.location customer_contact_id: header.location #check CustomerContact - name: check CustomerContact type: item method: GET path: '/${customer_contact_path}' conditions: is: code: 200 #create a System Contact - name: create a System Contact type: item method: POST path: /api/systemcontacts/ header: Content-Type: application/json content: firstname: sys_contact_first lastname: sys_contact_last email: sys_contact@syscontact.invalid conditions: is: code: 201 retain: system_contact_path: header.location system_contact_id: header.location #get System Contact - name: get System Contact type: item method: GET path: '/${system_contact_path}' retain: system_contact: body #create batch - name: create batch type: batch method: POST path: '/api/contracts/' iterations: 6 header: Content-Type: application/json content: status: active contact_id: ${system_contact_id} type: reseller billing_profile_id: ${billing_profile_id} retain: contract_path: header.location conditions: is: code: 201 #create invalid Contract with wrong type - name: create invalid Contract with wrong type type: item method: POST path: '/api/contracts/' header: Content-Type: application/json content: status: active contact_id: ${system_contact_id} billing_profile_id: ${billing_profile_id} type: invalid conditions: is: code: 422 body.code: 422 like: body.message: Validation failed.*type #create invalid Contract with wrong billing profile - name: create invalid Contract with wrong billing profile type: item method: POST path: '/api/contracts/' header: Content-Type: application/json content: status: active contact_id: ${system_contact_id} billing_profile_id: 999999 type: reseller conditions: is: code: 422 body.code: 422 like: body.message: Invalid 'billing_profile_id' #create invalid Contract with customercontact - name: create invalid Contract with customercontact type: item method: POST path: '/api/contracts/' header: Content-Type: application/json content: status: active type: reseller billing_profile_id: ${billing_profile_id} contact_id: ${customer_contact_id} conditions: is: code: 422 body.code: 422 like: body.message: The contact_id is not a valid ngcp:systemcontacts item #create invalid Contract without contact - name: create invalid Contract without contact type: item method: POST path: '/api/contracts/' header: Content-Type: application/json content: status: active type: reseller billing_profile_id: ${billing_profile_id} conditions: is: code: 422 #create invalid Contract with invalid status - name: create invalid Contract with invalid status type: item method: POST path: '/api/contracts/' header: Content-Type: application/json content: status: invalid type: reseller billing_profile_id: ${billing_profile_id} contact_id: ${system_contact_id} conditions: is: code: 422 body.code: 422 like: body.message: field='status' #verify pagination - name: verify pagination skip: 1 type: pagination method: GET path: '/api/contracts/?page=1&rows=5' retain: collection: body conditions: is: code: 200 #check options on contract - name: check OPTIONS on contract type: item method: OPTIONS path: '/${contract_path}' conditions: is: code: 200 ok: options: - GET - HEAD - OPTIONS - PUT - PATCH #get contract - name: GET contract type: item method: GET path: '/${contract_path}' retain: contract: body perl_code: !!perl/code | { my ($retained) = @_; map { delete $_->{effective_start_time}; $_; } @{$retained->{contract}->{all_billing_profiles}}; } conditions: is: code: 200 ok: '${contract}.status': defined '${contract}.type': defined '${contract}.all_billing_profiles': defined like: '${contract}.billing_profile_id': '[0-9]+' '${contract}.contact_id': '[0-9]+' '${contract}.id': '[0-9]+' is_deeply: '${contract}.all_billing_profiles': - profile_id: ${billing_profile_id} start: null stop: null #put contract with missing content-type - name: PUT contract with missing content-type type: item method: PUT path: '/${contract_path}' header: Prefer: return=minimal conditions: is: code: 415 #put contract with unsupported content type - name: PUT contract with unsupported Content-Type type: item method: PUT path: '/${contract_path}' header: Content-Type: application/xxx conditions: is: code: 415 #put contract with missing body - name: PUT contract with missing body type: item method: PUT path: '/${contract_path}' header: Content-Type: application/json Prefer: return=representation conditions: is: code: 400 #put contract - name: PUT contract type: item method: PUT path: '/${contract_path}' header: Content-Type: application/json Prefer: return=representation content: '${contract}' retain: new_contract: body perl_code: !!perl/code | { my ($retained) = @_; map { delete $_->{effective_start_time}; $_; } @{$retained->{new_contract}->{all_billing_profiles}}; } conditions: is: code: 200 is_deeply: '${contract}': ${new_contract} ok: '${new_contract}._links.ngcp:systemcontacts': defined '${new_contract}._links.ngcp:billingprofiles': defined #modify contract status - name: modify contract status type: item method: PATCH path: '/${contract_path}' header: Content-Type: application/json-patch+json Prefer: return=representation content: - op: replace path: /status value: pending retain: modified_contract: body conditions: is: code: 200 '${modified_contract}.status': pending '${modified_contract}._links.self.href': ${contract_path} '${modified_contract}._links.collection.href': /api/contracts/ #check patch with status undef - name: check patch with status undef type: item method: PATCH path: '/${contract_path}' header: Content-Type: application/json-patch+json Prefer: return=representation content: - op: replace path: /status value: null conditions: is: code: 422 #check patch with invalid status - name: check patch with invalid status type: item method: PATCH path: '/${contract_path}' header: Content-Type: application/json-patch+json Prefer: return=representation content: - op: replace path: /status value: invalid conditions: is: code: 422 #check patch with invalid contact_id - name: check patch with invalid contact_id type: item method: PATCH path: '/${contract_path}' header: Content-Type: application/json-patch+json Prefer: return=representation content: - op: replace path: /contact_id value: 99999 conditions: is: code: 422 #check patch with customer contact_id - name: check patch with customer contact_id type: item method: PATCH path: '/${contract_path}' header: Content-Type: application/json-patch+json Prefer: return=representation content: - op: replace path: /contact_id value: ${customer_contact_id} conditions: is: code: 422 #check patch with undef billing_profile_id - name: check patch with undef billing_profile_id type: item method: PATCH path: '/${contract_path}' header: Content-Type: application/json-patch+json Prefer: return=representation content: - op: replace path: /billing_profile_id value: null conditions: is: code: 422 #check patch with invalid billing_profile_id - name: check patch with invalid billing_profile_id type: item method: PATCH path: '/${contract_path}' header: Content-Type: application/json-patch+json Prefer: return=representation content: - op: replace path: /billing_profile_id value: 99999 conditions: is: code: 422 #multi-bill-prof: create another test billing profile - name: 'multi-bill-prof: create another test billing profile' type: item method: POST path: '/api/billingprofiles/' header: Content-Type: application/json Prefer: return=representation content: name: SECOND test profile ${unique_id} handle: second_testprofile${unique_id} reseller_id: 1 retain: second_billing_profile_id: header.location conditions: is: code: 201 perl_code: !!perl/code | { my ( $retained ) = @_; my $dtf = DateTime::Format::Strptime->new( pattern => '%F %T', ); #DateTime::Format::Strptime->new( pattern => '%Y-%m-%d %H:%M:%S' ); my $now = DateTime->now( time_zone => DateTime::TimeZone->new(name => 'local') ); my $t1 = $now->clone->add(days => 1); my $t2 = $now->clone->add(days => 2); my $t3 = $now->clone->add(days => 3); my $billing_profile_id = $retained->{billing_profile_id}; $retained->{malformed_profilemappings1} = [ { profile_id => $billing_profile_id, start => $dtf->format_datetime($now), stop => $dtf->format_datetime($now),} ]; $retained->{malformed_profilemappings2} = [ { profile_id => $billing_profile_id, start => $dtf->format_datetime($t1), stop => $dtf->format_datetime($t1),} ]; $retained->{malformed_profilemappings3} = [ { profile_id => $billing_profile_id, start => undef, stop => $dtf->format_datetime($now),},]; $retained->{malformed_profilemappings4} = [ { profile_id => $billing_profile_id, start => $dtf->format_datetime($t1), stop => $dtf->format_datetime($t2),}, ]; $retained->{correct_profile_mappings1} = [ { profile_id => $retained->{second_billing_profile_id}, start => undef, stop => undef, }, { profile_id => $billing_profile_id, start => $dtf->format_datetime($t1), stop => $dtf->format_datetime($t2), }, { profile_id => $billing_profile_id, start => $dtf->format_datetime($t2), stop => $dtf->format_datetime($t3), } ]; $retained->{correct_profile_mappings2} = [ { profile_id => $billing_profile_id, start => $dtf->format_datetime($t1), stop => $dtf->format_datetime($t2), }, { profile_id => $billing_profile_id, start => $dtf->format_datetime($t2), stop => $dtf->format_datetime($t3), }, { profile_id => $retained->{second_billing_profile_id}, start => $dtf->format_datetime($t3), stop => undef, } ]; } #multi-bill-prof POST: check 'start' timestamp is not in future - name: 'multi-bill-prof POST: check "start" timestamp is not in future' type: item method: POST path: '/api/contracts/' header: Content-Type: application/json content: status: active contact_id: ${system_contact_id} type: reseller max_subscriber: null external_id: null billing_profile_definition: profiles billing_profiles: ${malformed_profilemappings1} conditions: is: code: 422 #multi-bill-prof POST: check 'start' timestamp has to be before 'stop' timestamp - name: 'multi-bill-prof POST: check "start" timestamp has to be before "stop" timestamp' type: item method: POST path: '/api/contracts/' header: Content-Type: application/json content: status: active contact_id: ${system_contact_id} type: reseller max_subscriber: null external_id: null billing_profile_definition: profiles billing_profiles: ${malformed_profilemappings2} conditions: is: code: 422 #multi-bill-prof POST: check Interval with 'stop' timestamp but no 'start' timestamp specified - name: 'multi-bill-prof POST: check "Interval with "stop" timestamp but no "start" timestamp specified"' type: item method: POST path: '/api/contracts/' header: Content-Type: application/json content: status: active contact_id: ${system_contact_id} type: reseller max_subscriber: null external_id: null billing_profile_definition: profiles billing_profiles: ${malformed_profilemappings3} conditions: is: code: 422 #multi-bill-prof POST: check An initial interval without 'start' and 'stop' timestamps is required - name: 'multi-bill-prof POST: check An initial interval without "start" and "stop" timestamps is required' type: item method: POST path: '/api/contracts/' header: Content-Type: application/json content: status: active contact_id: ${system_contact_id} type: reseller max_subscriber: null external_id: null billing_profile_definition: profiles billing_profiles: ${malformed_profilemappings4} conditions: is: code: 422 #multi-bill-prof: create test contract - name: 'multi-bill-prof: create test contract' type: item method: POST path: '/api/contracts/' header: Content-Type: application/json content: status: active contact_id: ${system_contact_id} type: reseller max_subscriber: null external_id: null billing_profile_definition: profiles billing_profiles: ${correct_profile_mappings1} retain: contract_path: header.location conditions: is: code: 201 #get contract - name: GET contract type: item method: GET path: '/${contract_path}' retain: contract: body perl_code: !!perl/code | { my ($retained) = @_; map { delete $_->{effective_start_time}; $_; } @{$retained->{contract}->{all_billing_profiles}}; $retained->{malformed_profilemappings4} = [ { profile_id => $retained->{billing_profile_id}, start => undef, stop => undef,}, ]; } conditions: is: code: 200 '${contract}.billing_profile_id': ${second_billing_profile_id} ok: '${contract}.profile_package_id': undefined '${contract}.billing_profile_id': defined '${contract}.billing_profiles': defined '${contract}.all_billing_profiles': defined is_deeply: '${contract}.all_billing_profiles': ${correct_profile_mappings1} #multi-bill-prof PATCH: check 'start' timestamp is not in future - name: 'multi-bill-prof PATCH: check "start" timestamp is not in future' type: item method: PATCH path: '/${contract_path}' header: Content-Type: application/json-patch+json Prefer: return=representation content: - op: replace path: /billing_profiles value: ${malformed_profilemappings1} conditions: is: code: 422 #multi-bill-prof PATCH: check 'start' timestamp has to be before 'stop' timestamp - name: 'multi-bill-prof PATCH: check "start" timestamp has to be before "stop" timestamp' type: item method: PATCH path: '/${contract_path}' header: Content-Type: application/json-patch+json Prefer: return=representation content: - op: replace path: /billing_profiles value: ${malformed_profilemappings2} conditions: is: code: 422 #multi-bill-prof PATCH: check Interval with 'stop' timestamp but no 'start' timestamp specified - name: 'multi-bill-prof PATCH: check Interval with "stop" timestamp but no "start" timestamp specified' type: item method: PATCH path: '/${contract_path}' header: Content-Type: application/json-patch+json Prefer: return=representation content: - op: replace path: /billing_profiles value: ${malformed_profilemappings3} conditions: is: code: 422 #multi-bill-prof PATCH: check Adding intervals without 'start' and 'stop' timestamps is not allowed. - name: 'multi-bill-prof PATCH: check Adding intervals without "start" and "stop" timestamps is not allowed.' type: item method: PATCH path: '/${contract_path}' header: Content-Type: application/json-patch+json Prefer: return=representation content: - op: replace path: /billing_profiles value: ${malformed_profilemappings4} conditions: is: code: 422 #multi-bill-prof PATCH: test if patching profile_package_id fails - name: 'multi-bill-prof PATCH: test if patching profile_package_id fails' type: item method: PATCH path: '/${contract_path}' header: Content-Type: application/json-patch+json Prefer: return=representation content: - op: replace path: /profile_package_id value: null conditions: is: code: 422 #multi-bill-prof PATCH: test contract with new billing profile - name: 'multi-bill-prof PATCH: test contract with new billing profile' type: item method: PATCH path: '/${contract_path}' header: Content-Type: application/json-patch+json Prefer: return=representation retain: patched_contract: body content: - op: replace path: /billing_profile_id value: ${billing_profile_id} perl_code: !!perl/code | { my ($retained) = @_; $retained->{posted_profiles_number} = scalar @{$retained->{correct_profile_mappings1}} + 1; $retained->{actual_profiles_number} = scalar @{$retained->{patched_contract}->{all_billing_profiles}}; my $now = DateTime->now( time_zone => DateTime::TimeZone->new(name => 'local') ); foreach my $m ( @{$retained->{patched_contract}->{billing_profiles}} ) { if (!defined $m->{start}) { push(@{$retained->{expected_mappings}},$m); next; } my $s = $m->{start}; $s =~ s/^(\d{4}\-\d{2}\-\d{2})\s+(\d.+)$/$1T$2/; my $start = DateTime::Format::ISO8601->parse_datetime($s); $start->set_time_zone( DateTime::TimeZone->new(name => 'local') ); push(@{$retained->{expected_mappings}},$m) if ($start <= $now); } push ( @{$retained->{expected_mappings}}, @{$retained->{correct_profile_mappings2}} ); } conditions: is: code: 200 '${patched_contract}.billing_profile_id': ${billing_profile_id} '${posted_profiles_number}': '${actual_profiles_number}' ok: '${patched_contract}.profile_package_id': undefined '${patched_contract}.billing_profile_id': defined '${patched_contract}.billing_profiles': defined '${patched_contract}.all_billing_profiles': defined #multi-bill-prof: patch test contract - name: 'multi-bill-prof: patch test contract' type: item method: PATCH path: '/${contract_path}' header: Content-Type: application/json-patch+json Prefer: return=representation retain: patched_contract: body content: - op: replace path: /billing_profiles value: ${correct_profile_mappings2} conditions: is: code: 200 #get contract - name: GET contract type: item method: GET path: '/${contract_path}' retain: got_contract: body conditions: is: code: 200 '${patched_contract}.billing_profile_id': ${billing_profile_id} ok: '${patched_contract}.billing_profile_id': defined '${patched_contract}.billing_profiles': defined is_deeply: '${got_contract}': '${patched_contract}' #multi-bill-prof: put test contract - name: 'multi-bill-prof: put test contract' type: item method: PUT path: '/${contract_path}' header: Content-Type: application/json Prefer: return=representation retain: updated_contract: body content: status: active contact_id: ${system_contact_id} type: reseller max_subscriber: null external_id: null billing_profile_definition: profiles billing_profiles: ${correct_profile_mappings2} conditions: is: code: 200 #get contract - name: GET contract type: item method: GET path: '/${contract_path}' retain: got_contract: body conditions: is: code: 200 '${updated_contract}.billing_profile_id': ${billing_profile_id} ok: '${updated_contract}.billing_profile_id': defined '${updated_contract}.billing_profiles': defined is_deeply: '${got_contract}': '${updated_contract}' #try to delete contact before terminating contracts - name: try to delete contact before terminating contracts type: item method: DELETE path: '/api/systemcontacts/${system_contact_id}' perl_code: !!perl/code | { my ($retained) = @_; map { delete $_->{effective_start_time}; $_; } @{$retained->{patched_contract}->{billing_profiles}}; map { delete $_->{effective_start_time}; $_; } @{$retained->{updated_contract}->{billing_profiles}}; } conditions: is: code: 423 is_deeply: #perform tests against stripped mapping here, because deleting start_time would have affected previous is_deeply verification '${patched_contract}.billing_profiles': '${expected_mappings}' '${updated_contract}.billing_profiles': '${expected_mappings}' #multi-bill-prof: terminate contract - name: 'multi-bill-prof: terminate contract' type: item method: PATCH path: '/${contract_path}' header: Content-Type: application/json-patch+json Prefer: return=representation content: - op: replace path: /status value: terminated conditions: is: code: 200 #try to get already terminated contract - name: try to get already terminated contract type: item method: GET path: '/${contract_path}' conditions: is: code: 404 #terminate billingprofile - name: 'terminate billingprofile' type: item method: PATCH path: '/api/billingprofiles/${billing_profile_id}' header: Content-Type: application/json-patch+json Prefer: return=representation content: - op: replace path: /status value: terminated conditions: is: code: 200 #get contract 1 - name: 'get contract 1' type: item method: GET path: '/api/contracts/1' retain: contract1: body conditions: is: code: 200 '${contract1}.id': 1 '${contract1}.type': reseller #check contract 1 can't be terminated - name: 'check contract 1 can not be terminated' type: item method: PATCH path: '/api/contracts/1' header: Content-Type: application/json-patch+json Prefer: return=representation content: - op: replace path: /status value: terminated conditions: is: code: 403 #get contract 1 again to verify billing.schedule_contract_billing_profile_network proc contains no implicit commits - name: 'get contract 1 again to verify billing.schedule_contract_billing_profile_network proc contains no implicit commits' type: item method: GET path: '/api/contracts/1' retain: contract1: body conditions: is: code: 200 '${contract1}.id': 1 '${contract1}.type': reseller #check contract 1 can't be terminated again to verify billing.schedule_contract_billing_profile_network proc contains no implicit commits - name: 'check contract 1 can not be terminated again to verify billing.schedule_contract_billing_profile_network proc contains no implicit commits' type: item method: PATCH path: '/api/contracts/1' header: Content-Type: application/json-patch+json Prefer: return=representation content: - op: replace path: /status value: terminated conditions: is: code: 403