GDPR-IAB-TCFv2

 view release on metacpan or  search on metacpan

t/08-golden-validator.t  view on Meta::CPAN

use strict;
use warnings;

use Test::More;
use JSON::PP;
use FindBin;
use File::Spec;
use IO::Uncompress::Gunzip qw($GunzipError);
use lib 'lib';
use GDPR::IAB::TCFv2;
use GDPR::IAB::TCFv2::Validator;

# Replays the validator scenarios listed in t/corpus/validator_scenarios.pl
# against every TC string in the golden corpus and diffs the live results
# against the pre-recorded outcomes under tests.validator.<scenario_name>.
#
# To regenerate after intentionally changing validator behavior:
#     REGEN_CORPUS=1 prove -lr t/07-golden.t
# then inspect the diff and commit t/corpus/golden.jsonl.gz alongside the
# code change.

my $corpus_dir     = File::Spec->catdir($FindBin::Bin, 'corpus');
my $golden_file    = File::Spec->catfile($corpus_dir, 'golden.jsonl.gz');
my $scenarios_file = File::Spec->catfile($corpus_dir, 'validator_scenarios.pl');

if (!-f $golden_file) {
  plan skip_all => "Golden file $golden_file not found";
}
if (!-f $scenarios_file) {
  plan skip_all => "Scenario list $scenarios_file not found";
}

my $scenarios = do $scenarios_file or die "Could not load $scenarios_file: " . ($@ || $!);

# Pre-build the Validators once; they are pure functions of their args.
my @validators
  = map { {name => $_->{name}, validator => GDPR::IAB::TCFv2::Validator->new(%{$_->{args}}),} } @{$scenarios};

my $fh   = IO::Uncompress::Gunzip->new($golden_file) or die "Could not open $golden_file: $GunzipError";
my $json = JSON::PP->new->utf8;

my $count         = 0;
my $checked_count = 0;

while (my $line_json = <$fh>) {
  chomp $line_json;
  next unless $line_json;

  my $entry = $json->decode($line_json);
  $count++;

  # Parse-failure entries don't carry a validator section.
  next if $entry->{expect_failure};

  my $expected = $entry->{tests}{validator};
  next unless $expected;    # forward compat with pre-Phase-2 corpora

  $checked_count++;

  my $consent = eval {
    GDPR::IAB::TCFv2->Parse($entry->{tc_string}, json => {boolean_values => [JSON::PP::false, JSON::PP::true]});
  };
  if (my $err = $@) {
    fail("String $count: parse failed unexpectedly: $err");
    next;
  }

  for my $entry_v (@validators) {
    my $name        = $entry_v->{name};
    my $expected_sc = $expected->{$name};

    # If the scenario list has grown since the corpus was last
    # regenerated, skip rather than fail -- the user will hit this
    # legitimately on the regen iteration before they re-run prove.
    next unless $expected_sc;

    my $result = $entry_v->{validator}->validate_all($consent);
    my $got    = {valid => $result->is_valid ? JSON::PP::true : JSON::PP::false, reasons => [$result->reasons],};

    is_deeply $got, $expected_sc, "String $count: validator scenario '$name' matches golden";
  }
}

close $fh;

# Always emit at least one assertion so the file doesn't degrade to a
# bare `1..0` (which prove reports as exit 255 even when Perl returns 0).
# A corpus that pre-dates the validator section is legitimate -- it just
# means the section gets added on the next regen.
ok(1,
  $checked_count
  ? "replayed $checked_count corpus entries through validator scenarios"
  : "no tests.validator section in corpus -- " . "regenerate with REGEN_CORPUS=1 prove -lr t/07-golden.t");

done_testing;



( run in 0.527 second using v1.01-cache-2.11-cpan-acf6aa7dc9e )