Trog-Data-FlatFile-read.t 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. #!/usr/bin/env perl
  2. use 5.040;
  3. use re '/aasx';
  4. use warnings FATAL => 'all';
  5. use Test::More;
  6. use Test::Fatal qw{exception};
  7. use File::Path ();
  8. use FindBin;
  9. use FindBin::libs;
  10. my ( $ROOT, $OLDCWD );
  11. BEGIN {
  12. require File::Temp;
  13. require Cwd;
  14. $OLDCWD = Cwd::getcwd();
  15. $ROOT = File::Temp::tempdir( 'tcms-flatfile-XXXXXX', TMPDIR => 1, CLEANUP => 1 );
  16. # Text::Xslate and the loggers both compute paths from $HOME or the cwd at
  17. # load time, and $datastore is relative, so both have to be set before the
  18. # SUT is loaded.
  19. $ENV{HOME} = $ROOT;
  20. File::Path::make_path("$ROOT/data/files");
  21. File::Path::make_path("$ROOT/schema");
  22. # Trog::SQLite::TagIndex builds the tag index at load time and wants this
  23. # path, hardcoded and relative, before the SUT will even compile.
  24. require File::Copy;
  25. File::Copy::copy( "$FindBin::Bin/../schema/flatfile.schema", "$ROOT/schema/flatfile.schema" ) or die $!;
  26. chdir($ROOT) or die "could not chdir to the sandbox: $!";
  27. }
  28. END {
  29. chdir($OLDCWD) if $OLDCWD;
  30. }
  31. require_ok('Trog::Data::FlatFile') or BAIL_OUT("Can't find SUT");
  32. require_ok('Trog::Log') or BAIL_OUT("Can't find SUT");
  33. my $model = bless( {}, 'Trog::Data::FlatFile' );
  34. # Collect whatever the SUT says rather than letting it reach the terminal, so
  35. # that "did this warn" is a thing the test can assert on.
  36. my @said;
  37. local $SIG{__WARN__} = sub { push @said, $_[0] };
  38. sub reads {
  39. my ($id) = @_;
  40. @said = ();
  41. my $posts = $model->read( { id => $id, raw => 1 } );
  42. return ( $posts, join( '', @said ) );
  43. }
  44. open( my $fh, '>', 'data/files/good' ) or die $!;
  45. print {$fh} '[{"id":"good","title":"Good","tags":["public"],"visibility":"public"}]';
  46. close($fh) or die $!;
  47. subtest 'a post that is there' => sub {
  48. my ( $posts, $noise ) = reads('good');
  49. is( scalar(@$posts), 1, "the post comes back" );
  50. is( $posts->[0]{id}, 'good', "and it is the one asked for" );
  51. is( $noise, '', "quietly" );
  52. };
  53. subtest 'a post that is not there' => sub {
  54. # add() asks this before every single insert, to choose between version 0
  55. # and a bump. It used to name the file regardless of whether it existed,
  56. # so every new post reported itself as a failed read.
  57. my ( $posts, $noise ) = reads('no-such-post');
  58. is( scalar(@$posts), 0, "a miss is an empty list" );
  59. is( $noise, '', "and says nothing -- a miss is not a failure" );
  60. };
  61. subtest 'a post that cannot be parsed' => sub {
  62. open( my $bad, '>', 'data/files/corrupt' ) or die $!;
  63. print {$bad} '[{"id":"corrupt"';
  64. close($bad) or die $!;
  65. my ( $posts, $noise ) = reads('corrupt');
  66. is( scalar(@$posts), 0, "nothing comes back" );
  67. like( $noise, qr/corrupt/, "but it says which post, because this one is a real failure" );
  68. };
  69. subtest 'a post that cannot be read' => sub {
  70. SKIP: {
  71. skip( "running as root, which can read anything", 2 ) if $> == 0;
  72. open( my $no, '>', 'data/files/unreadable' ) or die $!;
  73. print {$no} 'x';
  74. close($no) or die $!;
  75. chmod( 0000, 'data/files/unreadable' ) or die $!;
  76. my ( $posts, $noise ) = reads('unreadable');
  77. is( scalar(@$posts), 0, "nothing comes back" );
  78. like( $noise, qr/unreadable/, "and it says which post" );
  79. chmod( 0644, 'data/files/unreadable' );
  80. }
  81. };
  82. subtest 'the log works before log_init()' => sub {
  83. # Logging is what you reach for when something has already gone wrong, so
  84. # dying because nobody set it up first turns a diagnostic into an outage.
  85. no warnings qw{once};
  86. is( $Trog::Log::log, undef, "no logger is configured in this process" );
  87. @said = ();
  88. is( exception { Trog::Log::WARN('a warning') }, undef, "WARN does not die" );
  89. like( join( '', @said ), qr/\[WARN\]\N*a[ ]warning/, "it lands on stderr instead" );
  90. @said = ();
  91. is( exception { Trog::Log::INFO('a note') }, undef, "INFO does not die" );
  92. is( exception { Trog::Log::DEBUG('a detail') }, undef, "DEBUG does not die" );
  93. # An uninitialized request id used to be spliced into the line as an
  94. # uninitialized-value warning.
  95. unlike( join( '', @said ), qr/uninitialized/, "and no uninitialized value creeps in" );
  96. like( join( '', @said ), qr/RequestId[ ]NONE/, "the request id says it has none" );
  97. # FATAL is fatal either way, or the caller's error handling disappears.
  98. like( exception { Trog::Log::FATAL('the roof is on fire') }, qr/the[ ]roof[ ]is[ ]on[ ]fire/, "FATAL still dies" );
  99. };
  100. done_testing();