0

I am trying to to test a type and I have a question: is table driven testing at all possible? I have a class which gets a hash passed to it and I would like to check it with different hash contents, so I would like my test to look like

require_relative '../../../spec_helper'
require_relative '../../isamjunction_mocks'

describe 'addon::isam::junctionmember::config', :type => :class do
    let(:pre_condition) {[$mock_isam_junction_member.render]}
    $junctionmember_hashes.each do |member|
        let(:params) {{:data_isamjunctionmember => member}}
        describe member[:name] do
            it {
                compare = member.dup
                compare.delete(:isam_servername)
                is_expected.to contain_class('isam_junction_member').with(
                    compare
                )
            }
        end
    end
end

When I run this it looks to me like the puppet catalog was compiled with the last of my array of test hashes because I see that when the first of my test hashes is compared against the catalog I get failures (looks like the catalog was compiled with my second and last hash (whose test succeeds).

In rspec-puppet when does the catalog get compiled? Can I force a re-compile and would this be performant? Is there a better, more idiomatic way of doing this?

$mock_isam_junction_member = MockResource.new 'isam_junction_member', {
  :params => {
    :reverseproxy_id => nil,
    :junction_point => nil,
    :junction_type => nil,
    :server_port => nil,
    :virtual_hostname => nil,
    :virtual_https_hostname => nil,
    :server_dn => nil,
    :query_contents => nil,
    :stateful_junction => nil,
    :case_sensitive_url => nil,
    :windows_style_url => nil,
    :https_port => nil,
    :http_port => nil,
    :proxy_hostname => nil,
    :proxy_port => nil,
    :sms_environment => nil,
    :vhost_label => nil,
    :admin_rest_url => nil,
    :admin_ssl => nil,
    :admin_verify_ssl => nil,
    :admin_rest_username => nil,
    :admin_rest_password => nil
  }
}

$junctionmember_hashes = [
  {
    :isam_servername => '145.70.150.207',
    :reverseproxy_id => 'reverseproxy_id',
    :name => 'http.backendserver.internalcorp.net',
    :junction_point => 'a-virtual-junction-point',
    :junction_type => 'tcp',
    :server_port => 1234,
    :virtual_hostname => 'afund.pensions.nl',
    :virtual_https_hostname => nil,
    :server_dn => 'distinguished.afund.pensions.nl',
    :query_contents => 'query_contents.exe',
    :stateful_junction => true,
    :case_sensitive_url => 'no',
    :windows_style_url => 'yes',
    :https_port => 443,
    :http_port => 80,
    :proxy_hostname => 'abc''',
    :proxy_port => 5678,
    :sms_environment => 'ghi',
    :vhost_label => 'xyz',
  }, {
    :isam_servername => '145.70.150.207',
    :reverseproxy_id => 'reverseproxy_id',
    :name => 'https.backendserver.internalcorp.net',
    :junction_point => 'a-virtual-junction-point',
    :junction_type => 'ssl',
    :server_port => 5678,
    :virtual_hostname => 'afund.pensions.nl',
    :server_dn => 'distinguished.afund.pensions.nl',
    :query_contents => 'query_contents.exe',
    :stateful_junction => true,
    :case_sensitive_url => 'no',
    :windows_style_url => 'yes',
    :https_port => 443,
    :sms_environment => 'abc',
    :vhost_label => 'def',
  }
]

And this is the code

class addon::isam::junctionmember::config (
  $data_isamjunctionmember = {}
) {

  $appliance_servername = $data_isamjunctionmember['isam_servername']

  $data_isamappliance = hiera('drdc_base', undef)['isamappliances'][$appliance_servername]

  $port = $data_isamjunctionmember['server_port']
  $protocol = $data_isamjunctionmember['server_protocol']
  $server_hostname = $data_isamjunctionmember['server_hostname']

  dsc_xfirewall {
    downcase("${appliance_servername}_IsamJunctionMember"):
      dsc_name        => 'DRDC-IsamJunctionMember',
      dsc_displayname => 'DRDC ISAM Junction Member',
      dsc_localport   => sprintf('%d', $port),
      dsc_protocol    => $protocol,
      dsc_profile     => ['Domain', 'Private', 'Public'],
      dsc_description => "Firewall rule to allow the ISAM server to forward traffic to a backend server. [${protocol}://${server_hostname}:${port}]"
  }

  class { 'isam_junction_member':
    reverseproxy_id        => $data_isamjunctionmember['reverseproxy_id'],
    name                   => $server_hostname,
    junction_point         => $data_isamjunctionmember['junction_point'],
    junction_type          => $data_isamjunctionmember['junction_type'],
    server_port            => $port,
    virtual_hostname       => $data_isamjunctionmember['virtual_hostname'],
    virtual_https_hostname => $data_isamjunctionmember['virtual_https_hostname'],
    server_dn              => $data_isamjunctionmember['server_dn'],
    query_contents         => $data_isamjunctionmember['query_contents'],
    stateful_junction      => $data_isamjunctionmember['stateful_junction'],
    case_sensitive_url     => $data_isamjunctionmember['case_sensitive_url'],
    windows_style_url      => $data_isamjunctionmember['windows_style_url'],
    https_port             => $data_isamjunctionmember['https_port'],
    http_port              => $data_isamjunctionmember['http_port'],
    proxy_hostname         => $data_isamjunctionmember['proxy_hostname'],
    proxy_port             => $data_isamjunctionmember['proxy_port'],
    sms_environment        => $data_isamjunctionmember['sms_environment'],
    vhost_label            => $data_isamjunctionmember['vhost_label'],
    admin_rest_url         => "https://${appliance_servername}",
    admin_ssl              => $data_isamappliance['admin_ssl'],
    admin_verify_ssl       => $data_isamappliance['admin_ssl_verify'],
    admin_rest_username    => $data_isamappliance['admin_rest_username'],
    admin_rest_password    => $data_isamappliance['admin_rest_password']
  }
}
smcp
  • 129
  • 2
  • 8
  • Can you clarify what is going on there in `$mock_isam_junction_member.render` and the meaning of your `pre_condition`? – Alex Harvey Jun 23 '17 at 12:18
  • Clarification: this is a spec test for a defined type and not a custom type. I was confused for a while what rspec-puppet had to with this or how this was working until re-reading the question a couple times and realizing the typo. – Matthew Schuchard Jun 23 '17 at 15:44
  • More to the point though, why not just iterate through the array of hashes? If you really want it stored elsewhere, then put the hashes in hiera and pull the data from there for each block. – Matthew Schuchard Jun 23 '17 at 15:46
  • @MattSchuchard The data is stored in hiera and passed to this class addon::isam::junctionmember, from a complete server definition. Everything works as I expected apart from the fact that when testing the first iteration the catalog appears to contain the values from the hash in the second iteration. I don't want to change the (someone elses) design just to get a test to work. – smcp Jun 24 '17 at 10:19
  • @AlexHarvey The type isam_junction_member is under construction, and I am trying to test against it's contract, so I have mocked it using rspec-puppet-utils, which are realy cool. – smcp Jun 24 '17 at 10:21
  • Rspec-puppet-utils is indeed cool, but I want to understand what you expect to be emitted by the `$mock_isam_junction_member.render` method, and what you have in `$junctionmember_hashes`. – Alex Harvey Jun 24 '17 at 10:29
  • Oh so when you say "second and last hash" you are referring to the same hash (union not intersection). – Matthew Schuchard Jun 24 '17 at 14:17
  • @AlexHarvey I added the mocks to the original question – smcp Jun 24 '17 at 14:58
  • @MattSchuchard yes, I said second and last because my suspicion is that the last hash has been applied to the catalog and, because of this, the type of table driven testing (an array of got,want situations) is not possible because of how rspec actually works. I've done a lot of this type of testing with Javascript and GO, but puppet DSL is of course not a 'programming language' – smcp Jun 24 '17 at 21:32
  • @smcp I tried to reproduce this but without your manifest code I am working in the dark a bit. I can say that I do this sort of thing all the time, loop through hashes etc and build catalogs for each. It does work. I can see some basic errors in your code already (e.g. your pre_conditions needs to also include the class, per docs). If you expand your question to include more code I'll have another go later. – Alex Harvey Jun 25 '17 at 12:08
  • @AlexHarvey I've added the manifest code. When you say "your pre_conditions include the class" which class do you mean? AFAICS the mock is the class, – smcp Jun 26 '17 at 04:37
  • I did not realise until I saw your manifest that your manifest is already declaring the isam_junction_member class. If it was not doing this, you would need to include it in the pre_condition. Glad you figured it out. – Alex Harvey Jun 26 '17 at 09:15

1 Answers1

1

Thanks to the suggestions from @AlexHarvey I found the answer. Nice to be rescued with someone with the same name as one of my teen heroes.

I reorganised the code slightly and now it works as expected. The trick seems to be that each iteration needs to have the full initialization done within a describe. It makes sense that everything for the test is within the same scope, but atm I don't understand completely the details of how this works (i.e. how and when the catalog compilation takes place).

require_relative '../../../spec_helper'
require_relative '../../isamjunction_mocks'

describe 'addon::isam::junctionmember::config', :type => :class do
    $junctionmember_hashes.each do |member|
        describe member[:name] do
            let(:pre_condition) {
                [
                  $mock_isam_junction_member.render
                ]
            }
            let(:facts) {{:operatingsystem => 'RedHat'}}
            let(:params) {{:data_isamjunctionmember => keys_to_s(member)}}
            it {
                    compare = member.dup
                    compare.delete(:isam_servername)
                    compare.delete(:name)
                    puts compare
                    is_expected.to contain_class('isam_junction_member').with(
                      compare
                    )
                }
        end
    end
end
smcp
  • 129
  • 2
  • 8