Trog-SQLite-TagIndex.t 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  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 Cwd ();
  8. use File::Copy ();
  9. use File::Path ();
  10. use File::Temp ();
  11. use FindBin;
  12. use FindBin::libs;
  13. use_ok('Trog::SQLite::TagIndex') or BAIL_OUT("Can't find SUT");
  14. # The index opens schema/ and data/ relative to the cwd, so it gets a root of
  15. # its own with the real schema in it.
  16. my $oldcwd = Cwd::getcwd();
  17. my $root = File::Temp::tempdir( CLEANUP => 1 );
  18. File::Path::make_path( "$root/schema", "$root/data" );
  19. File::Copy::copy( "$FindBin::Bin/../schema/flatfile.schema", "$root/schema/flatfile.schema" ) or die "Could not copy the schema: $!";
  20. chdir $root or die "Could not chdir to $root: $!";
  21. # A data model holding whatever posts it is given, answering read() and get()
  22. # the way build_index() and build_routes() ask.
  23. package Fake::Data {
  24. sub new { my ( $class, @posts ) = @_; return bless( { posts => \@posts }, $class ) }
  25. sub read { my ($self) = @_; return [ @{ $self->{posts} } ] }
  26. sub get { my ($self) = @_; return @{ $self->{posts} } }
  27. }
  28. # One that gets as far as the index, then falls over before the routes.
  29. package Broken::Data {
  30. use parent -norequire, 'Fake::Data';
  31. sub get { die "the data model fell over\n" }
  32. }
  33. sub post {
  34. my (%over) = @_;
  35. return {
  36. id => 'p-one',
  37. tags => [qw{blog public}],
  38. created => 1700000000,
  39. local_href => '/posts/p-one',
  40. method => 'GET',
  41. callback => 'Trog::Routes::HTML::posts',
  42. aliases => [],
  43. %over,
  44. };
  45. }
  46. sub index_rows {
  47. my $rows = Trog::SQLite::TagIndex::dbh()->selectall_arrayref( 'SELECT id, tag FROM posts ORDER BY id, tag', { Slice => {} } );
  48. return [ map { "$_->{id}:$_->{tag}" } @$rows ];
  49. }
  50. subtest 'add_post' => sub {
  51. my $data = Fake::Data->new();
  52. Trog::SQLite::TagIndex::add_post( post(), $data );
  53. Trog::SQLite::TagIndex::add_post( post(), $data );
  54. is_deeply( index_rows(), [qw{p-one:blog p-one:public}], 'saving a post twice indexes its tags once' );
  55. Trog::SQLite::TagIndex::add_post( post( tags => ['public'] ), $data );
  56. is_deeply( index_rows(), ['p-one:public'], 'and a tag the edit removed is gone' );
  57. is_deeply( [ Trog::SQLite::TagIndex::posts_for_tags('blog') ], [], 'so the post no longer turns up under it' );
  58. };
  59. subtest 'rebuild' => sub {
  60. my $dbh = Trog::SQLite::TagIndex::dbh();
  61. # What an index looks like after something other than tCMS changed the
  62. # posts: a post that is gone, and a route to it.
  63. Trog::SQLite::TagIndex::add_post( post( id => 'p-gone', local_href => '/posts/p-gone', aliases => ['/gone'] ), Fake::Data->new() );
  64. $dbh->do('INSERT INTO posts_index (post_id, post_time, tag_id) SELECT post_id, post_time, tag_id FROM posts_index');
  65. my $data = Fake::Data->new( post( tags => [qw{blog public}] ), post( id => 'p-two', tags => ['public'], local_href => '/posts/p-two' ) );
  66. Trog::SQLite::TagIndex::rebuild($data);
  67. is_deeply( index_rows(), [qw{p-one:blog p-one:public p-two:public}], 'the index holds exactly the posts there are, once each' );
  68. my %routes = Trog::SQLite::TagIndex::routes();
  69. is_deeply( [ sort keys %routes ], [qw{/posts/p-one /posts/p-two}], 'and the routes of those posts only' );
  70. is_deeply( { Trog::SQLite::TagIndex::aliases() }, {}, 'with the gone post\'s alias gone too' );
  71. my ($methods) = $dbh->selectrow_array('SELECT COUNT(*) FROM methods');
  72. ok( $methods, 'the HTTP methods the schema fills are kept' );
  73. Trog::SQLite::TagIndex::rebuild($data);
  74. is_deeply( index_rows(), [qw{p-one:blog p-one:public p-two:public}], 'running it again changes nothing' );
  75. };
  76. subtest 'rebuild is one transaction' => sub {
  77. my $before = index_rows();
  78. my $broken = Broken::Data->new( post() );
  79. my $err = exception { Trog::SQLite::TagIndex::rebuild($broken) };
  80. ok( $err, 'a rebuild that fails part way dies' );
  81. like( $err, qr/fell[ ]over/, 'with the error that stopped it' );
  82. is_deeply( index_rows(), $before, 'and leaves the index as it was' );
  83. };
  84. chdir $oldcwd;
  85. done_testing();