---
#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
