Class-Simple-Cached

 view release on metacpan or  search on metacpan

t/integration.t  view on Meta::CPAN

	package t::Int::CachedA;
	our @ISA = ('Class::Simple::Cached');
}

{
	# CSC subclass B — its keys are prefixed with 't::Int::CachedB:'
	package t::Int::CachedB;
	our @ISA = ('Class::Simple::Cached');
}

{
	# An inner object that can respond to can() and isa() for delegation tests.
	package t::Int::Delegating;
	sub new      { bless {}, shift }
	sub greet    { 'hello' }
	sub can {
		my ($self, $m) = @_;
		return ($m eq 'greet') ? \&greet : undef;
	}
	sub isa {
		my ($self, $class) = @_;
		return ($class eq 't::Int::Delegating') ? 1 : 0;
	}
}

# ─────────────────────────────────────────────────────────────────────────────
# Workflow 1 — Hash-ref backend complete lifecycle
#
# Strategy: exercise new → setter → repeated getter (cache hits) → DESTROY.
# After DESTROY we inspect the hash ref directly to confirm keys are gone.
# This proves the full round-trip without any CHI involvement.
# ─────────────────────────────────────────────────────────────────────────────

subtest 'workflow:hash:lifecycle' => sub {
	# Strategy: construct with a hash ref, set a value, read it back twice
	# (second read must be a cache hit), then let DESTROY run and verify
	# the hash ref is emptied.
	my %store;
	my $inner = t::Int::Inner->new();
	my $obj   = Class::Simple::Cached->new(cache => \%store, object => $inner);

	# Setter stores value in both the inner object and the hash ref.
	$obj->colour('red');
	is($obj->colour(), 'red',
		'hash:lifecycle: getter returns value after setter');

	# The cache entry should be in the hash under the prefixed key.
	ok(exists $store{"${PREFIX}colour"},
		'hash:lifecycle: cache key exists in the hash ref');

	# Second getter — inner object must NOT be called again (cache hit).
	my $pre_count = $inner->call_count();
	$obj->colour();
	is($inner->call_count(), $pre_count,
		'hash:lifecycle: second getter does not invoke the inner object');

	diag sprintf('[hash:lifecycle] store keys: %s', join(', ', sort keys %store))
		if $ENV{TEST_VERBOSE};

	# DESTROY fires when $obj goes out of scope; use a block to force it.
	{ my $tmp = $obj; }    # keeps $obj alive; we destroy via explicit undef
	$obj = undef;

	is(scalar(keys %store), 0,
		'hash:lifecycle: DESTROY removes all cache entries from the hash ref');

	done_testing();
};

# ─────────────────────────────────────────────────────────────────────────────
# Workflow 2 — Optional CHI dependency
#
# Strategy: block CHI with Test::Without::Module to prove that CSC itself
# never issues a `require CHI` — CHI is a purely caller-supplied runtime
# dependency.  The module is already loaded and continues to work with a
# hash-ref cache even when CHI cannot be required.
#
# After the subtest, CHI is unblocked so subsequent subtests can load it.
# ─────────────────────────────────────────────────────────────────────────────

subtest 'workflow:optional-chi:hash-backend-works-without-chi' => sub {
	# Block CHI from loading to simulate an environment where it is not installed.
	Test::Without::Module->import('CHI');

	# Verify the block is effective for new require calls.
	eval { require CHI };
	ok($@, 'optional-chi: CHI cannot be required when blocked by Test::Without::Module');

	diag "[optional-chi] require CHI error: $@" if $ENV{TEST_VERBOSE} && $@;

	# CSC itself never calls require CHI — it only interacts with whatever object
	# the caller passes as cache =>.  A hash ref works with no CHI in the process.
	my $obj = Class::Simple::Cached->new(cache => {});
	$obj->field('present');
	is($obj->field(), 'present',
		'optional-chi: hash-ref backend operates correctly without CHI');

	# Likewise, a duck-typed CHI-compatible object works without the CHI module.
	my $fake_chi = t::Int::CHI->new();
	my $obj2 = Class::Simple::Cached->new(cache => $fake_chi, object => t::Int::Inner->new());
	$obj2->colour('blue');
	is($obj2->colour(), 'blue',
		'optional-chi: duck-typed CHI-compatible object works without CHI in process');

	Test::Without::Module->unimport('CHI');

	done_testing();
};

# ─────────────────────────────────────────────────────────────────────────────
# Workflow 3 — CHI backend complete lifecycle with spy verification
#
# Strategy: construct with a t::Int::CHI mock, call a setter then a getter,
# and inspect the call log to prove:
#   (a) set() was called with the 'never' expiry token
#   (b) the second get() did NOT re-hit the inner object (cache hit)
# DESTROY on $obj should call purge() on the CHI mock.
# ─────────────────────────────────────────────────────────────────────────────

SKIP: {
	eval { require CHI };



( run in 0.749 second using v1.01-cache-2.11-cpan-14f38c9f855 )