diff --git a/Rakefile b/Rakefile index 385ae6c..e3109bd 100644 --- a/Rakefile +++ b/Rakefile @@ -110,25 +110,26 @@ end namespace :cache do task :config do # rubocop:disable Rake/Desc - require_relative 'config/environment' # load config info + require_relative 'app/infrastructure/cache/local_cache' require_relative 'app/infrastructure/cache/redis_cache' + require_relative 'config/environment' # load config info @api = CodePraise::App end desc 'Directory listing of local dev cache' namespace :list do desc 'Lists development cache' - task :dev do + task :dev => :config do puts 'Lists development cache' - list = `ls _cache/rack/meta` - puts 'No local cache found' if list.empty? - puts list + keys = CodePraise::Cache::Local.new(@api.config).keys + puts 'No local cache found' if keys.none? + keys.each { |key| puts "Key: #{key}" } end desc 'Lists production cache' task :production => :config do puts 'Finding production cache' - keys = CodePraise::Cache::Client.new(@api.config).keys + keys = CodePraise::Cache::Remote.new(@api.config).keys puts 'No keys found' if keys.none? keys.each { |key| puts "Key: #{key}" } end @@ -136,9 +137,10 @@ namespace :cache do namespace :wipe do desc 'Delete development cache' - task :dev do + task :dev => :config do puts 'Deleting development cache' - sh 'rm -rf _cache/*' + CodePraise::Cache::Local.new(@api.config).wipe + puts 'Development cache wiped' end desc 'Delete production cache' @@ -146,7 +148,7 @@ namespace :cache do print 'Are you sure you wish to wipe the production cache? (y/n) ' if $stdin.gets.chomp.downcase == 'y' puts 'Deleting production cache' - wiped = CodePraise::Cache::Client.new(@api.config).wipe + wiped = CodePraise::Cache::Remote.new(@api.config).wipe wiped.each { |key| puts "Wiped: #{key}" } end end diff --git a/app/infrastructure/cache/local_cache.rb b/app/infrastructure/cache/local_cache.rb new file mode 100644 index 0000000..a9f6d8b --- /dev/null +++ b/app/infrastructure/cache/local_cache.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require 'fileutils' + +module CodePraise + module Cache + # Local disk cache utility + class Local + def initialize(config) + @cache_dir = config.LOCAL_CACHE + ensure_cache_directory + end + + def keys + Dir.glob("#{@cache_dir}/**/*").select { |f| File.file?(f) } + end + + def wipe + FileUtils.rm_rf(Dir.glob("#{@cache_dir}/*")) + end + + private + + def ensure_cache_directory + FileUtils.mkdir_p(@cache_dir) + end + end + end +end diff --git a/app/infrastructure/cache/redis_cache.rb b/app/infrastructure/cache/redis_cache.rb index 6544046..2ba5cef 100644 --- a/app/infrastructure/cache/redis_cache.rb +++ b/app/infrastructure/cache/redis_cache.rb @@ -5,7 +5,7 @@ module CodePraise module Cache # Redis client utility - class Client + class Remote def initialize(config) @redis = Redis.new(url: config.REDISCLOUD_URL) end diff --git a/config/environment.rb b/config/environment.rb index c2249b4..f959df0 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -29,8 +29,8 @@ def self.config = Figaro.env configure :development do use Rack::Cache, verbose: true, - metastore: 'file:_cache/rack/meta', - entitystore: 'file:_cache/rack/body' + metastore: "#{config.LOCAL_CACHE}/meta", + entitystore: "#{config.LOCAL_CACHE}/body" end configure :production do diff --git a/config/secrets_example.yml b/config/secrets_example.yml index 942b655..04a7646 100644 --- a/config/secrets_example.yml +++ b/config/secrets_example.yml @@ -5,12 +5,14 @@ development: DB_FILENAME: db/local/dev.db GITHUB_TOKEN: create_github_token REPOSTORE_PATH: repostore + LOCAL_CACHE: _cache/rack API_HOST: http://localhost:9090 REDISCLOUD_URL: url-assigned-by-Redis-provider-on-Heroku app_test: DB_FILENAME: db/local/test.db REPOSTORE_PATH: repostore + LOCAL_CACHE: _cache/rack API_HOST: http://localhost:9090 REDISCLOUD_URL: url-assigned-by-Redis-provider-on-Heroku @@ -18,6 +20,7 @@ test: DB_FILENAME: db/local/test.db GITHUB_TOKEN: create_github_token REPOSTORE_PATH: repostore + LOCAL_CACHE: _cache/rack API_HOST: http://localhost:9090 REDISCLOUD_URL: url-assigned-by-Redis-provider-on-Heroku diff --git a/coverage/.resultset.json b/coverage/.resultset.json index 6a7fc3e..c2f1e83 100644 --- a/coverage/.resultset.json +++ b/coverage/.resultset.json @@ -16,7 +16,7 @@ 1, null, 1, - 55, + 56, null, null ] @@ -746,7 +746,7 @@ null, null, 1, - 2, + 1, null, null, 1, @@ -758,6 +758,39 @@ null ] }, + "/Users/soumyaray/Sync/Dropbox/ossdev/classes/SOA-class/projects/soa2025/api-codepraise-2025/app/infrastructure/cache/local_cache.rb": { + "lines": [ + null, + null, + 1, + null, + 1, + 1, + null, + 1, + 1, + 5, + 5, + null, + null, + 1, + 13, + null, + null, + 1, + 2, + null, + null, + 1, + null, + 1, + 5, + null, + null, + null, + null + ] + }, "/Users/soumyaray/Sync/Dropbox/ossdev/classes/SOA-class/projects/soa2025/api-codepraise-2025/app/infrastructure/cache/redis_cache.rb": { "lines": [ null, @@ -1010,16 +1043,16 @@ 1, null, 1, - 193, - 193, - 193, - 193, + 192, + 192, + 192, + 192, null, null, 1, - 2, - 2, - 2, + 1, + 1, + 1, null, null, 1, @@ -1034,21 +1067,21 @@ null, null, 1, - 2, - 2, + 1, + 1, null, null, 1, - 2, - 2, + 1, + 1, null, null, 1, - 386, + 384, null, null, 1, - 193, + 192, null, null, null, @@ -1059,8 +1092,8 @@ null, null, 1, - 1, - 5, + 0, + 0, null, null, null, @@ -1395,15 +1428,15 @@ null, null, 1, - 15, + 14, null, null, null, 1, - 2, 1, + 0, null, - 6, + 0, null, null, null @@ -1437,8 +1470,8 @@ null, null, 1, - 6, - 1, + 0, + 0, null, null, 1, @@ -1457,7 +1490,7 @@ null, null, 1, - 32, + 31, null, null, null, @@ -1505,7 +1538,7 @@ null, null, 1, - 1, + 0, null, null, null, @@ -2530,8 +2563,8 @@ null, 1, 30, - 1930, - 1930, + 2070, + 2070, null, null, 30, @@ -3081,6 +3114,97 @@ null ] }, + "/Users/soumyaray/Sync/Dropbox/ossdev/classes/SOA-class/projects/soa2025/api-codepraise-2025/spec/tests/unit/local_cache_spec.rb": { + "lines": [ + null, + null, + 1, + 1, + null, + 1, + 1, + null, + null, + 5, + 5, + 5, + null, + null, + null, + null, + null, + 1, + null, + null, + 5, + null, + null, + 1, + 1, + null, + 1, + null, + null, + 1, + 1, + null, + null, + 1, + 1, + 1, + 1, + 1, + 1, + null, + 1, + null, + 1, + 1, + 1, + null, + null, + 1, + 1, + null, + 1, + null, + 1, + null, + null, + 1, + 1, + null, + null, + 1, + 1, + 1, + 1, + 1, + 1, + null, + null, + 1, + null, + null, + 1, + null, + null, + 1, + null, + 1, + null, + null, + 1, + 1, + null, + null, + 1, + null, + 1, + null, + null + ] + }, "/Users/soumyaray/Sync/Dropbox/ossdev/classes/SOA-class/projects/soa2025/api-codepraise-2025/spec/tests/unit/result_spec.rb": { "lines": [ null, @@ -3111,6 +3235,6 @@ ] } }, - "timestamp": 1763966828 + "timestamp": 1764424989 } } diff --git a/spec/tests/unit/local_cache_spec.rb b/spec/tests/unit/local_cache_spec.rb new file mode 100644 index 0000000..3be3f3f --- /dev/null +++ b/spec/tests/unit/local_cache_spec.rb @@ -0,0 +1,87 @@ +# frozen_string_literal: true + +require_relative '../../helpers/spec_helper' +require 'fileutils' + +describe 'Unit test of LocalCache' do + before do + # SAFETY: Create a mock config that returns a TEST cache directory + # This prevents tests from touching the real cache at _cache/rack + @test_cache_dir = 'spec/fixtures/test_cache' + @config = Minitest::Mock.new + @config.expect(:LOCAL_CACHE, @test_cache_dir) + + # When LocalCache.new(@config) is called, it will use @test_cache_dir + # instead of the real LOCAL_CACHE value from secrets.yml + end + + after do + # Clean up the temporary test cache directory after each test + # The real cache directory (_cache/rack) is never touched + FileUtils.rm_rf(@test_cache_dir) + end + + it 'should create cache directory if it does not exist' do + CodePraise::Cache::Local.new(@config) + + _(Dir.exist?(@test_cache_dir)).must_equal true + end + + it 'should list keys when cache has files' do + cache = CodePraise::Cache::Local.new(@config) + + # Create some test files in the TEST directory + meta_dir = "#{@test_cache_dir}/meta" + body_dir = "#{@test_cache_dir}/body" + FileUtils.mkdir_p(meta_dir) + FileUtils.mkdir_p(body_dir) + FileUtils.touch("#{meta_dir}/test1") + FileUtils.touch("#{body_dir}/test2") + + keys = cache.keys + + _(keys.length).must_equal 2 + _(keys).must_include "#{meta_dir}/test1" + _(keys).must_include "#{body_dir}/test2" + end + + it 'should return empty array when no cache files exist' do + cache = CodePraise::Cache::Local.new(@config) + + keys = cache.keys + + _(keys).must_be_empty + end + + it 'should wipe all cache files' do + cache = CodePraise::Cache::Local.new(@config) + + # Create some test files in the TEST directory + meta_dir = "#{@test_cache_dir}/meta" + body_dir = "#{@test_cache_dir}/body" + FileUtils.mkdir_p(meta_dir) + FileUtils.mkdir_p(body_dir) + FileUtils.touch("#{meta_dir}/test1") + FileUtils.touch("#{body_dir}/test2") + + # Verify files exist before wipe + _(cache.keys.length).must_equal 2 + + # Wipe cache - only affects TEST directory, not real cache + cache.wipe + + # Verify files are gone + _(cache.keys).must_be_empty + # But directory should still exist (wipe removes contents, not directory) + _(Dir.exist?(@test_cache_dir)).must_equal true + end + + it 'should handle wipe when cache is already empty' do + cache = CodePraise::Cache::Local.new(@config) + + # Should not raise error when wiping empty cache + cache.wipe + + _(cache.keys).must_be_empty + end +end