AmberDB
view release on metacpan or search on metacpan
t/amberdb_ecommerce_facet.t view on Meta::CPAN
record_index => 1,
blocks => [
{ id => "id", type => "auto_id" },
{ id => "name", type => "text" },
]
} );
$adb->insert_id( 'catalog_contributor', 100, 'Mustafa Kemal Atatürk' );
$adb->insert_id( 'catalog_contributor', 200, 'Franz Kafka' );
$adb->insert_id( 'catalog_contributor', 300, 'Frank Herbert' );
$adb->insert_id( 'catalog_contributor', 400, 'Dostoyevski' );
# 2. Setup Main Product Table with Facets, Search, Junk and RDBM
$adb->table_attr( 'catalog_product', {
record_index => 1,
use_junk => 1,
use_facet => 1,
junk_rules => [ [ 6, "ne", 1 ] ], # status != 1 -> junk
facet_rules => [ [ 6, "eq", 1 ] ], # status == 1 -> active in facet
search_block => [ 4 ], # Search on title
match_block => [ 1, 2, 3, 5, 6 ],
facet_block => [
{ blk => 1, id => 'cat', label => 'Kategori', table => 'catalog_category', name_idx => 1 },
{ blk => 2, id => 'pub', label => 'Yayınevi', table => 'catalog_producer', name_idx => 1 },
{ blk => 3, id => 'auth', label => 'Yazar', table => 'catalog_contributor', name_idx => 1 },
{ blk => 5, id => 'format', label => 'Kapak', rdbm => '' }, # .unq based
],
blocks => [
{ id => "id", type => "auto_id" }, # 0
{ id => "cat", type => "text", rdbm => "catalog_category;1" }, # 1 (Category)
{ id => "pub", type => "text", rdbm => "catalog_producer;1" }, # 2 (Publisher)
{ id => "auth", type => "text", rdbm => "catalog_contributor;1" }, # 3 (Author)
{ id => "title", type => "text" }, # 4 (Title)
{ id => "format", type => "text" }, # 5 (Ciltli / Karton)
{ id => "status", type => "option" }, # 6 (1:Aktif, 0:Pasif)
{ id => "price", type => "text" }, # 7 (Fiyat)
]
} );
# 3. Ingest Test Catalog
# Record: [ id, cat, pub, auth, title, format, status, price ]
my @products = (
[ 1, 1, 10, 200, 'DönüÅüm', 'Karton', 1, 85.00 ],
[ 2, 1, 10, 200, 'Dava', 'Ciltli', 1, 150.00 ],
[ 3, 1, 20, 400, 'Suç ve Ceza', 'Ciltli', 1, 195.00 ],
[ 4, 1, 20, 400, 'Karamazov KardeÅler', 'Karton', 1, 220.00 ],
[ 5, 2, 20, 100, 'Nutuk', 'Ciltli', 1, 250.00 ],
[ 6, 2, 20, 100, 'Geometri', 'Karton', 1, 75.00 ],
[ 7, 3, 30, 300, 'Dune', 'Ciltli', 1, 280.00 ],
[ 8, 3, 30, 300, 'Dune Mesihi', 'Karton', 1, 160.00 ],
[ 9, 3, 30, 300, 'Dune Ãocukları (Pasif)', 'Karton', 0, 140.00 ], # JUNK / Inactive
);
$adb->insert_list( 'catalog_product', @products );
# ---------------------------------------------------------------------------
subtest '1. Initial Unfiltered Facet Menu & Label Resolution' => sub {
plan tests => 8;
my $menu = $adb->facet_menu(
'catalog_product',
{}, # No filter selected
$adb->table_attr('catalog_product', 'facet_block'),
{ sort => 'count' }
);
ok( $menu, "Facet menu generated successfully" );
is( $menu->{count}, 8, "Total active products count is 8 (junk item excluded)" );
is( scalar( @{ $menu->{groups} } ), 4, "4 facet groups returned" );
# Check Category Group (blk 1)
my ($cat_group) = grep { $_->{blk} == 1 } @{ $menu->{groups} };
ok( $cat_group, "Category facet group exists" );
is( $cat_group->{name}, 'Kategori', "Category label correct" );
# Roman (id: 1) has 4 products (IDs 1, 2, 3, 4)
my ($roman_item) = grep { $_->{label} eq 'Roman' } @{ $cat_group->{records} };
ok( $roman_item, "RDBM resolved 'Roman' name" );
is( $roman_item->{count}, 4, "'Roman' category count is 4" );
# Dune author Frank Herbert (id: 300) has 2 active products (ID 7, 8; ID 9 is passive)
my ($auth_group) = grep { $_->{blk} == 3 } @{ $menu->{groups} };
my ($herbert_item) = grep { $_->{label} eq 'Frank Herbert' } @{ $auth_group->{records} };
is( $herbert_item->{count}, 2, "Frank Herbert count is 2 (inactive 3rd book excluded)" );
};
# ---------------------------------------------------------------------------
subtest '2. Single Dimension Filtering & Cross-Filter Counts (Disjunctive Faceting)' => sub {
plan tests => 6;
# Filter: Category = Roman (cat: 1)
my $menu = $adb->facet_menu(
'catalog_product',
{ 1 => 1 }, # Category = 1 (Roman)
$adb->table_attr('catalog_product', 'facet_block'),
{ sort => 'count' }
);
is( $menu->{count}, 4, "Filtered result has 4 Roman books" );
is_deeply( [ sort { $a <=> $b } @{ $menu->{ids} } ], [ 1, 2, 3, 4 ], "Correct product IDs returned" );
# Category menu itself (blk 1) should still display other category counts under disjunctive faceting!
my ($cat_group) = grep { $_->{blk} == 1 } @{ $menu->{groups} };
my ($tarih_item) = grep { $_->{label} eq 'Tarih' } @{ $cat_group->{records} };
is( $tarih_item->{count}, 2, "Disjunctive facet preserves other categories count (Tarih: 2)" );
# Publisher menu (blk 2) should ONLY count publishers within Roman!
my ($pub_group) = grep { $_->{blk} == 2 } @{ $menu->{groups} };
my ($can_pub) = grep { $_->{label} eq 'Can Yayınları' } @{ $pub_group->{records} };
my ($is_pub) = grep { $_->{label} eq 'İŠBankası Kültür Yayınları' } @{ $pub_group->{records} };
my ($ithaki_pub) = grep { $_->{label} eq 'İthaki Yayınları' } @{ $pub_group->{records} };
is( $can_pub->{count}, 2, "Can Yayınları has 2 Roman books" );
is( $is_pub->{count}, 2, "İŠBankası has 2 Roman books" );
ok( !$ithaki_pub, "İthaki Yayınları has 0 Roman books (correctly omitted)" );
};
# ---------------------------------------------------------------------------
subtest '3. Multi-Dimension Filtering (Category + Publisher + Format)' => sub {
plan tests => 4;
# Filter: Category = Roman (1) AND Publisher = Can Yayınları (10) AND Format = Ciltli (in .unq format is resolved)
my $menu = $adb->facet_menu(
'catalog_product',
{ 1 => 1, 2 => 10, 5 => 'Ciltli' },
$adb->table_attr('catalog_product', 'facet_block'),
{ sort => 'count' }
);
is( $menu->{count}, 1, "Only 1 book matches all 3 criteria" );
is_deeply( $menu->{ids}, [2], "Product ID 2 ('Dava' by Kafka) returned" );
my ($auth_group) = grep { $_->{blk} == 3 } @{ $menu->{groups} };
my ($kafka_item) = grep { $_->{label} eq 'Franz Kafka' } @{ $auth_group->{records} };
is( $kafka_item->{count}, 1, "Author Kafka has 1 matching item" );
is( scalar( @{ $auth_group->{records} } ), 1, "Only Kafka is in Author facet" );
};
# ---------------------------------------------------------------------------
subtest '4. Faceted Navigation over Full-Text Search Results (base_ids)' => sub {
plan tests => 4;
# Search keyword "Dune" (should match IDs 7, 8; ID 9 is junk)
my @hit_ids = $adb->search_table( 'catalog_product', 'Dune', keys_only => 1, jnktype => 'A' );
is( scalar(@hit_ids), 2, "Search found 2 active Dune books" );
# Apply facet menu over search hits
my $menu = $adb->facet_menu(
'catalog_product',
{},
$adb->table_attr('catalog_product', 'facet_block'),
{ base_ids => \@hit_ids }
);
is( $menu->{count}, 2, "Facet menu restricted to 2 search hits" );
my ($cat_group) = grep { $_->{blk} == 1 } @{ $menu->{groups} };
is( scalar( @{ $cat_group->{records} } ), 1, "Only Bilim Kurgu category present in search facets" );
is( $cat_group->{records}->[0]->{label}, 'Bilim Kurgu', "Category is Bilim Kurgu" );
};
# ---------------------------------------------------------------------------
subtest '5. Dynamic Price Range (Min/Max Slider) & Facet Integration' => sub {
plan tests => 5;
# Scenario: User selects Price range [100.00 TL - 200.00 TL]
# In catalog_product, prices are:
# 1: 85.00 (Roman, Can, Kafka) -> OUT (< 100)
# 2: 150.00 (Roman, Can, Kafka) -> IN
# 3: 195.00 (Roman, Is Bankasi, Dostoyevski) -> IN
# 4: 220.00 (Roman, Is Bankasi, Dostoyevski) -> OUT (> 200)
# 5: 250.00 (Tarih, Is Bankasi, Ataturk) -> OUT (> 200)
# 6: 75.00 (Tarih, Is Bankasi, Ataturk) -> OUT (< 100)
# 7: 280.00 (Bilim Kurgu, Ithaki, Herbert) -> OUT (> 200)
# 8: 160.00 (Bilim Kurgu, Ithaki, Herbert) -> IN
my $min_price = 100.00;
my $max_price = 200.00;
# Read active products price column to get matching IDs
my ( undef, @all_active_ids ) = $adb->index_get( $adb->table_path('catalog_product') . ".inx", "A:keys" );
my @price_matched_ids;
for my $id (@all_active_ids) {
my @row = $adb->read_id( 'catalog_product', $id );
my $price = $row[7] + 0;
if ( $price >= $min_price && $price <= $max_price ) {
push @price_matched_ids, $id;
}
}
is_deeply( [ sort { $a <=> $b } @price_matched_ids ], [ 2, 3, 8 ], "Price range [100 - 200] matches IDs 2, 3, 8" );
# Generate Facet menu constrained by the dynamic price slider (base_ids)
my $menu = $adb->facet_menu(
'catalog_product',
{},
$adb->table_attr('catalog_product', 'facet_block'),
{ base_ids => \@price_matched_ids }
);
is( $menu->{count}, 3, "Facet menu restricted to 3 price-matched products" );
# Category counts within price range [100 - 200]
my ($cat_group) = grep { $_->{blk} == 1 } @{ $menu->{groups} };
my ($roman_item) = grep { $_->{label} eq 'Roman' } @{ $cat_group->{records} };
my ($scifi_item) = grep { $_->{label} eq 'Bilim Kurgu' } @{ $cat_group->{records} };
is( $roman_item->{count}, 2, "Roman has 2 books in price range [100-200]" );
is( $scifi_item->{count}, 1, "Bilim Kurgu has 1 book in price range [100-200]" );
# Tarih has 0 books between 100-200 (75 and 250), so it should not appear
my ($tarih_item) = grep { $_->{label} eq 'Tarih' } @{ $cat_group->{records} };
ok( !$tarih_item, "Tarih category has 0 items in [100-200] range and is omitted" );
};
done_testing();
( run in 0.719 second using v1.01-cache-2.11-cpan-e623d60df62 )