use strict;
use warnings;

use Test::More;
use Test::Exception;

use GDPR::IAB::TCFv2;

subtest "bitfield" => sub {
  subtest "valid tcf v2 consent string using bitfield" => sub {
    my $consent;

    my $tc_string = 'CLcVDxRMWfGmWAVAHCENAXCkAKDAADnAABRgA5mdfCKZuYJez-NQm0TBMYA4oCAAGQYIAAAAAAEAIAEgAA';
    lives_ok {
      $consent = GDPR::IAB::TCFv2->Parse($tc_string);
    }
    'should not throw exception';

    isa_ok $consent, 'GDPR::IAB::TCFv2', 'gdpr iab tcf v2 consent';

    is $consent->tc_string, $tc_string, 'should return the original tc string';

    is "${consent}", $tc_string, 'should return the original tc string in string context';

    is $consent->version, 2, 'should return version 2';

    is $consent->created, 1228644257, 'should return the creation epoch 07/12/2008';

    {
      my ($seconds, $nanoseconds) = $consent->created;
      is $seconds,     1228644257, 'should return the creation epoch 07/12/2008 on list context';
      is $nanoseconds, 700000000,  'should return the 700000000 nanoseconds of epoch on list context';
    }

    is $consent->last_updated, 1326215413, 'should return the last update epoch 10/01/2012';

    {
      my ($seconds, $nanoseconds) = $consent->last_updated;
      is $seconds,     1326215413, 'should return the last updated epoch 07/12/2008 on list context';
      is $nanoseconds, 400000000,  'should return the 400000000 nanoseconds of epoch on list context';
    }

    is $consent->cmp_id, 21, 'should return the cmp id 21';

    is $consent->cmp_version, 7, 'should return the cmp version 7';

    is $consent->consent_screen, 2, 'should return the consent screen 2';

    is $consent->consent_language, "EN", 'should return the consent language "EN"';

    is $consent->vendor_list_version, 23, 'should return the vendor list version 23';

    is $consent->policy_version, 2, 'should return the policy version 2';

    ok $consent->is_service_specific, 'should return true for service specific';

    ok !$consent->use_non_standard_stacks, 'should return false for use non standard stacks';

    ok !$consent->purpose_one_treatment, 'should return false for use purpose one treatment';

    is $consent->publisher_country_code, "KM", 'should return the publisher country code "KM"';

    is $consent->max_vendor_id_consent, 115, "max vendor id consent is 115";

    is $consent->max_vendor_id_legitimate_interest, 113, "max vendor id legitimate interest is 113";

    subtest "check purpose consent ids" => sub {
      plan tests => 24;

      my %allowed_purposes = map { $_ => 1 } (1, 3, 9, 10);

      foreach my $id (1 .. 24) {
        is !!$consent->is_purpose_consent_allowed($id), !!$allowed_purposes{$id}, "checking purpose id $id for consent";
      }
    };

    subtest "check purpose legitimate interest ids" => sub {
      plan tests => 24;

      my %allowed_purposes = map { $_ => 1 } (3, 4, 5, 8, 9, 10);

      foreach my $id (1 .. 24) {
        is !!$consent->is_purpose_legitimate_interest_allowed($id), !!$allowed_purposes{$id},
          "checking purpose id $id for legitimate interest";
      }
    };

    subtest "check special feature opt in" => sub {
      plan tests => 12;

      my %special_feature_opt_in = (2 => 1,);

      foreach my $id (1 .. 12) {
        is !!$consent->is_special_feature_opt_in($id), !!$special_feature_opt_in{$id},
          "checking special feature id $id opt in";
      }
    };

    subtest "check vendor consent ids" => sub {
      plan tests => 120;

      my %allowed_vendors = map { $_ => 1 } (
        2,  3,  6,  7,  8,  10, 12, 13, 14, 15, 16,  21,  25,  27,  30,  31, 34, 35, 37, 38,
        39, 42, 43, 49, 52, 54, 55, 56, 57, 59, 60,  63,  64,  65,  66,  67, 68, 69, 73, 74,
        76, 78, 83, 86, 87, 89, 90, 92, 96, 99, 100, 106, 109, 110, 114, 115
      );

      foreach my $id (1 .. 120) {
        is !!$consent->vendor_consent($id), !!$allowed_vendors{$id}, "checking vendor id $id for consent";
      }
    };

    subtest "check vendor legitimate interest ids" => sub {
      plan tests => 120;

      my %allowed_vendors = map { $_ => 1 } (1, 9, 26, 27, 30, 36, 37, 43, 86, 97, 110, 113);

      foreach my $id (1 .. 120) {
        is !!$consent->vendor_legitimate_interest($id), !!$allowed_vendors{$id},
          "checking vendor id $id for legitimate interest";
      }
    };

    ok !$consent->check_publisher_restriction(1, 0, 284),
      "should have no publisher restriction to vendor 284 regarding purpose id 1 of type 0 'Purpose Flatly Not Allowed by Publisher' when called with positional parameters";

    ok !$consent->check_publisher_restriction(purpose_id => 1, restriction_type => 0, vendor_id => 284),
      "should have no publisher restriction to vendor 284 regarding purpose id 1 of type 0 'Purpose Flatly Not Allowed by Publisher' when called with named parameters";

    my $restrictions = $consent->publisher_restrictions(284);
    is_deeply $restrictions, {}, "should return the restriction purpose id => restriction map type map";

    my $publisher_tc = $consent->publisher_tc;

    ok !defined($publisher_tc), "should not return publisher_tc";

    done_testing;
  };


  subtest "valid tcf v2 consent string using bitfield with publisher TC section" => sub {

    subtest "without custom purposes" => sub {
      my $consent;

      my $tc_string
        = 'CLcVDxRMWfGmWAVAHCENAXCkAKDAADnAABRgA5mdfCKZuYJez-NQm0TBMYA4oCAAGQYIAAAAAAEAIAEgAA.argAC0gAAAAAAAAAAAA';
      lives_ok {
        $consent = GDPR::IAB::TCFv2->Parse($tc_string);
      }
      'should not throw exception';

      isa_ok $consent, 'GDPR::IAB::TCFv2', 'gdpr iab tcf v2 consent';

      is $consent->tc_string, $tc_string, 'should return the original tc string';

      is "${consent}", $tc_string, 'should return the original tc string in string context';

      is $consent->version, 2, 'should return version 2';

      my $publisher_tc = $consent->publisher_tc;

      ok defined($publisher_tc), "should return publisher_tc";

      is $publisher_tc->num_custom_purposes, 0, "should not have any custom purposes";

      subtest "check publisher purpose consent ids" => sub {
        plan tests => 24;

        my %allowed_purposes = map { $_ => 1 } (2, 4, 6, 8, 9, 10);

        foreach my $id (1 .. 24) {
          is !!$publisher_tc->is_purpose_consent_allowed($id), !!$allowed_purposes{$id},
            "checking publisher purpose id $id for consent";
        }
      };

      subtest "check publisher purpose legitimate interest ids" => sub {
        plan tests => 24;

        my %allowed_purposes = map { $_ => 1 } (2, 4, 5, 7, 10);

        foreach my $id (1 .. 24) {
          is !!$publisher_tc->is_purpose_legitimate_interest_allowed($id), !!$allowed_purposes{$id},
            "checking publisher purpose id $id for legitimate interest";
        }
      };

      done_testing;
    };

    subtest "with custom purposes" => sub {
      my $consent;

      my $tc_string = 'CLcVDxRMWfGmWAVAHCENAXCkAKDAADnAABRgA5mdfCKZuYJez-NQm0TBMYA4oCAAGQYIAAAAAAEAIAEgAA.YAAAAAAAAXA';
      lives_ok {
        $consent = GDPR::IAB::TCFv2->Parse($tc_string);
      }
      'should not throw exception';

      isa_ok $consent, 'GDPR::IAB::TCFv2', 'gdpr iab tcf v2 consent';

      is $consent->tc_string, $tc_string, 'should return the original tc string';

      is "${consent}", $tc_string, 'should return the original tc string in string context';

      is $consent->version, 2, 'should return version 2';

      my $publisher_tc = $consent->publisher_tc;

      ok defined($publisher_tc), "should return publisher_tc";

      is $publisher_tc->num_custom_purposes, 2, "should have 2 custom purposes";

      subtest "check publisher purpose consent ids" => sub {
        plan tests => 24;

        my %allowed_purposes;

        foreach my $id (1 .. 24) {
          is !!$publisher_tc->is_purpose_consent_allowed($id), !!$allowed_purposes{$id},
            "checking publisher purpose id $id for consent";
        }
      };

      subtest "check publisher purpose legitimate interest ids" => sub {
        plan tests => 24;

        my %allowed_purposes;

        foreach my $id (1 .. 24) {
          is !!$publisher_tc->is_purpose_legitimate_interest_allowed($id), !!$allowed_purposes{$id},
            "checking publisher purpose id $id for legitimate interest";
        }
      };


      subtest "check publisher custom purpose consent ids" => sub {
        plan tests => 2;

        ok $publisher_tc->is_custom_purpose_consent_allowed(1), "should have custom purpose 1 allowed";
        ok $publisher_tc->is_custom_purpose_consent_allowed(2), "should have custom purpose 2 allowed";
      };

      subtest "check publisher custom purpose legitimate interest ids" => sub {
        plan tests => 2;

        ok $publisher_tc->is_custom_purpose_legitimate_interest_allowed(1),  "should have custom purpose 1 allowed";
        ok !$publisher_tc->is_custom_purpose_legitimate_interest_allowed(2), "should not have custom purpose 2 allowed";
      };

      done_testing;
    };

    done_testing;
  };
  done_testing;

};

subtest "range" => sub {
  subtest "valid tcf v2 consent string using range" => sub {
    my $consent;

    my $tc_string = 'COyfVVoOyfVVoADACHENAwCAAAAAAAAAAAAAE5QBgALgAqgD8AQACSwEygJyAnSAMABgAFkAgQCDASeAmYBOgAA';
    lives_ok {
      $consent = GDPR::IAB::TCFv2->Parse($tc_string);
    }
    'should not throw exception';

    isa_ok $consent, 'GDPR::IAB::TCFv2', 'gdpr iab tcf v2 consent';

    is $consent->tc_string, $tc_string, 'should return the original tc string';

    is "${consent}", $tc_string, 'should return the original tc string in string context';

    is $consent->version, 2, 'should return version 2';

    is $consent->created, 1587946020, 'should return the creation epoch 27/04/2020 on scalar context';

    {
      my ($seconds, $nanoseconds) = $consent->created;
      is $seconds,     1587946020, 'should return the creation epoch 27/04/2020 on list context';
      is $nanoseconds, 0,          'should return the 0 nanoseconds of epoch on list context';
    }

    is $consent->last_updated, 1587946020, 'should return the last update epoch 27/04/2020';

    {
      my ($seconds, $nanoseconds) = $consent->last_updated;
      is $seconds,     1587946020, 'should return the last update epoch 27/04/2020 on list context';
      is $nanoseconds, 0,          'should return the 0 nanoseconds of epoch on list context';
    }

    is $consent->cmp_id, 3, 'should return the cmp id 3';

    is $consent->cmp_version, 2, 'should return the cmp version 2';

    is $consent->consent_screen, 7, 'should return the consent screen 7';

    is $consent->consent_language, "EN", 'should return the consent language "EN"';

    is $consent->vendor_list_version, 48, 'should return the vendor list version 23';

    is $consent->policy_version, 2, 'should return the policy version 2';

    ok !$consent->is_service_specific, 'should return true for service specific';

    ok !$consent->use_non_standard_stacks, 'should return false for use non standard stacks';

    ok !$consent->purpose_one_treatment, 'should return false for use purpose one treatment';

    is $consent->publisher_country_code, "AA", 'should return the publisher country code "AA"';

    is $consent->max_vendor_id_consent, 626, "max vendor id consent is 626";

    is $consent->max_vendor_id_legitimate_interest, 628, "max vendor id legitimate interest is 628";

    subtest "check purpose consent ids" => sub {
      plan tests => 24;

      foreach my $id (1 .. 24) {
        ok !$consent->is_purpose_consent_allowed($id), "checking purpose id $id for consent";
      }
    };

    subtest "check purpose legitimate interest ids" => sub {
      plan tests => 24;

      foreach my $id (1 .. 24) {
        ok !$consent->is_purpose_legitimate_interest_allowed($id), "checking purpose id $id for legitimate interest";
      }
    };

    subtest "check special feature opt in" => sub {
      plan tests => 12;

      foreach my $id (1 .. 12) {
        ok !$consent->is_special_feature_opt_in($id), "checking special feature id $id opt in";
      }
    };

    subtest "check vendor consent ids" => sub {
      plan tests => 626;

      my %allowed_vendors = map { $_ => 1 } (23, 42, 126, 127, 128, 587, 613, 626);

      foreach my $id (1 .. 626) {
        is !!$consent->vendor_consent($id), !!$allowed_vendors{$id}, "checking vendor id $id for consent";
      }
    };

    subtest "check vendor legitimate interest ids" => sub {
      plan tests => 628;

      my %allowed_vendors = map { $_ => 1 } (24, 44, 129, 130, 131, 591, 614, 628);

      foreach my $id (1 .. 628) {
        is !!$consent->vendor_legitimate_interest($id), !!$allowed_vendors{$id},
          "checking vendor id $id for legitimate interest";
      }
    };

    ok !$consent->check_publisher_restriction(1, 0, 284),
      "should have no publisher restriction to vendor 284 regarding purpose id 1 of type 0 'Purpose Flatly Not Allowed by Publisher'";

    my $restrictions = $consent->publisher_restrictions(284);
    is_deeply $restrictions, {}, "should return the restriction purpose id => restriction map type map";

    my $publisher_tc = $consent->publisher_tc;

    ok !defined($publisher_tc), "should not return publisher_tc";

    done_testing;
  };
  subtest "valid tcf v2 consent string using range and prefetch some vendor ids" => sub {
    my $consent;

    my $tc_string = 'COyfVVoOyfVVoADACHENAwCAAAAAAAAAAAAAE5QBgALgAqgD8AQACSwEygJyAnSAMABgAFkAgQCDASeAmYBOgAA';
    lives_ok {
      $consent = GDPR::IAB::TCFv2->Parse($tc_string, prefetch => [1, 20 .. 24, 284, 626, 268]);
    }
    'should not throw exception';

    isa_ok $consent, 'GDPR::IAB::TCFv2', 'gdpr iab tcf v2 consent';

    is $consent->tc_string, $tc_string, 'should return the original tc string';

    is "${consent}", $tc_string, 'should return the original tc string in string context';

    is $consent->version, 2, 'should return version 2';


    subtest "check vendor consent ids" => sub {
      plan tests => 626;

      my %allowed_vendors = map { $_ => 1 } (23, 42, 126, 127, 128, 587, 613, 626);

      foreach my $id (1 .. 626) {
        is !!$consent->vendor_consent($id), !!$allowed_vendors{$id}, "checking vendor id $id for consent";
      }
    };

    subtest "check vendor legitimate interest ids" => sub {
      plan tests => 628;

      my %allowed_vendors = map { $_ => 1 } (24, 44, 129, 130, 131, 591, 614, 628);

      foreach my $id (1 .. 628) {
        is !!$consent->vendor_legitimate_interest($id), !!$allowed_vendors{$id},
          "checking vendor id $id for legitimate interest";
      }
    };

    ok !$consent->check_publisher_restriction(1, 0, 284),
      "should have no publisher restriction to vendor 284 regarding purpose id 1 of type 0 'Purpose Flatly Not Allowed by Publisher'";

    my $restrictions = $consent->publisher_restrictions(284);
    is_deeply $restrictions, {}, "should return the restriction purpose id => restriction map type map";

    done_testing;
  };
  done_testing;
};
subtest "check publisher restriction" => sub {
  subtest "check publisher restriction #1" => sub {
    my $consent;

    lives_ok {
      $consent = GDPR::IAB::TCFv2->Parse('COwAdDhOwAdDhN4ABAENAPCgAAQAAv___wAAAFP_AAp_4AI6ACACAA');
    }
    'should not throw exception';

    isa_ok $consent, 'GDPR::IAB::TCFv2', 'gdpr iab tcf v2 consent';

    is $consent->version, 2, 'should return version 2';

    ok !$consent->check_publisher_restriction(1, 0, 284),
      "should have no publisher restriction to vendor 284 regarding purpose id 1 of type 0 'Purpose Flatly Not Allowed by Publisher'";

    ok $consent->check_publisher_restriction(7, 1, 32),
      "must have publisher restriction to vendor 32 regarding purpose id 7 of type 1 'Require Consent'";

    ok $consent->check_publisher_restriction(purpose_id => 7, restriction_type => 1, vendor_id => 32),
      "hash form: must have publisher restriction to vendor 32 regarding purpose id 7 of type 1 'Require Consent'";

    ok !$consent->check_publisher_restriction(purpose_id => 7, restriction_type => 1, vendor_id => 7),
      "hash form: should have no publisher restriction to vendor 7 regarding purpose id 7 of type 1 'Require Consent'";

    ok !$consent->check_publisher_restriction(7, 1, 7),
      "must have publisher restriction to vendor 7 regarding purpose id 7 of type 1 'Require Consent'";

    ok !$consent->check_publisher_restriction(5, 1, 32),
      "must have publisher restriction to vendor 32 regarding purpose id 5 of type 1 'Require Consent'";

    my $restrictions = $consent->publisher_restrictions(284);
    is_deeply $restrictions, {}, "should return the restriction purpose id => restriction map type map";

    $restrictions = $consent->publisher_restrictions(32);
    is_deeply $restrictions, {7 => {1 => 1}}, "should return the restriction purpose id => restriction map type map";

    done_testing;
  };

  subtest "check publisher restriction #2" => sub {
    my $consent;

    lives_ok {
      $consent = GDPR::IAB::TCFv2->Parse(
        'COxPe2TOxPe2TALABAENAPCgAAAAAAAAAAAAAFAAAAoAAA4IACACAIABgACAFA4ADACAAIygAGADwAQBIAIAIB0AEAEBSACACAA');
    }
    'should not throw exception';

    isa_ok $consent, 'GDPR::IAB::TCFv2', 'gdpr iab tcf v2 consent';

    is $consent->version, 2, 'should return version 2';

    ok !$consent->purpose_one_treatment, 'should have no purpose 1 treatment';

    ok !$consent->is_special_feature_opt_in(3), 'should have no feature id 3 opt in';

    ok !$consent->check_publisher_restriction(1, 0, 284),
      "should have no publisher restriction to vendor 284 regarding purpose id 1 of type 0 'Purpose Flatly Not Allowed by Publisher'";

    ok $consent->check_publisher_restriction(1, 0, 32),
      "must have publisher restriction to vendor 32 regarding purpose id 1 of type 0 'Purpose Flatly Not Allowed by Publisher'";

    ok !$consent->check_publisher_restriction(1, 1, 32),
      "should have no publisher restriction to vendor 32 regarding purpose id 1 of type 1 'Require Consent'";

    ok $consent->check_publisher_restriction(2,  0, 32);
    ok $consent->check_publisher_restriction(2,  0, 5);
    ok $consent->check_publisher_restriction(2,  0, 11);
    ok !$consent->check_publisher_restriction(2, 0, 44);
    ok !$consent->check_publisher_restriction(2, 0, 500);
    ok $consent->check_publisher_restriction(2,  1, 32);
    ok !$consent->check_publisher_restriction(2, 1, 42);

    my $restrictions = $consent->publisher_restrictions(284);
    is_deeply $restrictions, {}, "should return the restriction purpose id => restriction map type map";

    $restrictions = $consent->publisher_restrictions(32);
    is_deeply $restrictions, {1 => {0 => 1}, 2 => {0 => 1, 1 => 1}, 7 => {0 => 1, 1 => 1}, 10 => {0 => 1, 1 => 1}},
      "should return the restriction purpose id => restriction map type map";


    done_testing;
  };

  subtest "check publisher restriction #3 " => sub {
    my $consent;

    lives_ok {
      $consent = GDPR::IAB::TCFv2->Parse('COzSDo9OzSDo9B9AAAENAiCAALAAAAAAAAAACOQAQCOAAAAA');
    }
    'should not throw exception';

    isa_ok $consent, 'GDPR::IAB::TCFv2', 'gdpr iab tcf v2 consent';

    is $consent->version, 2, 'should return version 2';

    ok !$consent->check_publisher_restriction(1, 0, 284),
      "should have no publisher restriction to vendor 284 regarding purpose id 1 of type 0 'Purpose Flatly Not Allowed by Publisher'";

    my $restrictions = $consent->publisher_restrictions(284);
    is_deeply $restrictions, {}, "should return the restriction purpose id => restriction map type map";

    done_testing;
  };

  done_testing;
};

subtest "invalid tcf consent string candidates" => sub {
  throws_ok {
    GDPR::IAB::TCFv2->Parse();
  }
  qr/missing gdpr consent string/, 'undefined consent string should throw error';

  throws_ok {
    GDPR::IAB::TCFv2->Parse("");
  }
  qr/missing gdpr consent string/, 'empty consent string should throw error';

  lives_ok {
    my $consent = GDPR::IAB::TCFv2->Parse("BOEFEAyOEFEAyAHABDENAI4AAAB9vABAASAAAAAAAAAA");

    is $consent->version, 1, "tcf v1";

  }
  'valid tcf v1 consent string should now throw error without strict flag';

  throws_ok {
    GDPR::IAB::TCFv2->Parse("BOEFEAyOEFEAyAHABDENAI4AAAB9vABAASAAAAAAAAAA", strict => 1);
  }
  qr/consent string is not tcf version 2/, 'valid tcf v1 consent string should throw error with strict flag';

  throws_ok {
    GDPR::IAB::TCFv2->Parse("Clc");
  }
  qr/vendor consent strings are at least 29 bytes long/,
    'short (less than 29 bytes) tcf v2 consent string should throw error';

  lives_ok {
    my $consent = GDPR::IAB::TCFv2->Parse("DOEFEAyOEFEAyAHABDENAI4AAAB9vABAASAAAAAAAAAA", strict => 0);
    is $consent->version, 3, "tcf v3";
  }
  'possible tcf v3 consent string should not throw error without strict flag';

  throws_ok {
    GDPR::IAB::TCFv2->Parse("DOEFEAyOEFEAyAHABDENAI4AAAB9vABAASAAAAAAAAAA", strict => 1);
  }
  qr/consent string is not tcf version 2/, 'possible tcf v3 consent string should throw error with strict flag';

  throws_ok {
    GDPR::IAB::TCFv2->Parse("COyfVVoOyfVVoADACHENAwCAAAAAAAAAAAAAE5QBgALgAqgD8AQACSwEygJyAnSAMABgAFkAgQCDASeAmYBOgA!A");
  }
  qr/invalid base64 format/, 'string is not a base64 url encoded string';

  throws_ok {
    GDPR::IAB::TCFv2->Parse('COvcSpYOvcSpYC9AAAENAPCAAAAAAAAAAAAAAFAAAAA')
  }
  qr/invalid consent data: no legitimate interest start position/,
    'this test uses a crafted consent uses bit field, declares 10 vendors and legitimate interest without required content';

  throws_ok {
    GDPR::IAB::TCFv2->Parse('COvcSpYOvcSpYC9AAAENAPCAAAAAAAAAAAAAAFQBgAAgABAACAAEAAQAAgAA')
  }
  qr/invalid consent data: no legitimate interest start position/,
    'this test uses a crafted consent uses range section, declares 10 vendors, 6 exceptions and legitimate interest without require';


  done_testing;
};

subtest "check if looks like tcf v2 consent string" => sub {
  ok GDPR::IAB::TCFv2::looksLikeIsConsentVersion2(
    "COyfVVoOyfVVoADACHENAwCAAAAAAAAAAAAAE5QBgALgAqgD8AQACSwEygJyAnSAMABgAFkAgQCDASeAmYBOgAA"),
    "this valid consent string starts with literal 'C' so it looks like a tcf v2 consent string";

  ok GDPR::IAB::TCFv2::looksLikeIsConsentVersion2(
    "COyfVVoOyfVVoADACHENAwCAAAAAAAAAAAAAE5QBgALgAqgD8AQACSwEygJyAnSAMABgAFkAgQCDASeAmYBOgA!A"),
    "this invalid consent string starts with literal 'C' so it looks like a tcf v2 consent string";

  ok !GDPR::IAB::TCFv2::looksLikeIsConsentVersion2("BOEFEAyOEFEAyAHABDENAI4AAAB9vABAASAAAAAAAAAA"),
    "this tcf v1 consent string starts with literal 'B' so it does not looks like a tcf v2";

  ok !GDPR::IAB::TCFv2::looksLikeIsConsentVersion2(""), "empty consent string does not looks like a tcf v2";

  ok !GDPR::IAB::TCFv2::looksLikeIsConsentVersion2(), "no consent string does not looks like a tcf v2";

  done_testing;
};

subtest "multi-purpose and vendor explicit methods" => sub {
  my $tc_string = 'CLcVDxRMWfGmWAVAHCENAXCkAKDAADnAABRgA5mdfCKZuYJez-NQm0TBMYA4oCAAGQYIAAAAAAEAIAEgAA';
  my $consent   = GDPR::IAB::TCFv2->Parse($tc_string);

  subtest "is_purpose_consent_allowed with multiple IDs" => sub {
    ok $consent->is_purpose_consent_allowed(1,  3), "purposes 1 and 3 are allowed";
    ok !$consent->is_purpose_consent_allowed(1, 2), "purpose 2 is not allowed, so (1, 2) is false";
    throws_ok { $consent->is_purpose_consent_allowed() }
    qr/method is_purpose_consent_allowed requires at least one argument/, "throws if no arguments";
  };

  subtest "is_purpose_legitimate_interest_allowed with multiple IDs" => sub {
    ok $consent->is_purpose_legitimate_interest_allowed(3,  4), "purposes 3 and 4 are allowed";
    ok !$consent->is_purpose_legitimate_interest_allowed(3, 1), "purpose 1 is not allowed, so (3, 1) is false";
    throws_ok { $consent->is_purpose_legitimate_interest_allowed() }
    qr/method is_purpose_legitimate_interest_allowed requires at least one argument/, "throws if no arguments";
  };

  subtest "is_vendor_consent_allowed" => sub {

    # Weborama (284) is not in this string (max vendor 115)
    ok !$consent->is_vendor_consent_allowed(284, 1), "vendor 284 is not in consent list";

    # Vendor 2 is in consent list, purpose 1 is allowed
    ok $consent->is_vendor_consent_allowed(2, 1), "vendor 2, purpose 1 allowed";

    # Vendor 2, purposes 1 and 3 allowed
    ok $consent->is_vendor_consent_allowed(2, 1, 3), "vendor 2, purposes 1 and 3 allowed";

    # Vendor 2, purpose 2 NOT allowed
    ok !$consent->is_vendor_consent_allowed(2, 2), "vendor 2, purpose 2 NOT allowed";
  };

  subtest "is_vendor_legitimate_interest_allowed" => sub {

    # Vendor 9 is in LI list, purpose 3 is allowed
    ok $consent->is_vendor_legitimate_interest_allowed(9, 3), "vendor 9, purpose 3 allowed";

    # Vendor 9, purpose 1 NOT allowed
    ok !$consent->is_vendor_legitimate_interest_allowed(9, 1), "vendor 9, purpose 1 NOT allowed";
  };

  subtest "is_vendor_allowed_for_any_basis" => sub {

    # Vendor 2 (Consent)
    ok $consent->is_vendor_allowed_for_any_basis(2, 1), "vendor 2 allowed for purpose 1 (via consent)";

    # Vendor 9 (LI)
    ok $consent->is_vendor_allowed_for_any_basis(9, 3), "vendor 9 allowed for purpose 3 (via LI)";

    # Vendor 1000 (Neither)
    ok !$consent->is_vendor_allowed_for_any_basis(1000, 1), "vendor 1000 not allowed";
  };

  subtest "vendor methods with publisher restrictions" => sub {
    my $tc_restricted = 'COwAdDhOwAdDhN4ABAENAPCgAAQAAv___wAAAFP_AAp_4AI6ACACAA';
    my $c             = GDPR::IAB::TCFv2->Parse($tc_restricted);

    # Type 1 restriction for vendor 32 on purpose 7 means MUST use Consent.
    # So is_vendor_legitimate_interest_allowed(32, 7) must be false.
    ok !$c->is_vendor_legitimate_interest_allowed(32, 7),
      "vendor 32, purpose 7: restricted Type 1 (Require Consent) -> LI allowed is false";

# In this string, vendor 32 and purpose 7 consent bits are 0, so it should be false.
    ok !$c->is_vendor_consent_allowed(32, 7), "vendor 32, purpose 7: consent bit is 0 -> consent allowed is false";
  };

  done_testing;
};

done_testing;
