diff --git a/backend/build.gradle b/backend/build.gradle index 7815278..a7b1870 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -19,8 +19,10 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter' implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-cache' testImplementation 'org.springframework.boot:spring-boot-starter-test' implementation 'org.jsoup:jsoup:1.14.3' + implementation 'com.github.ben-manes.caffeine:caffeine:3.1.8' } spotless { diff --git a/backend/src/main/java/com/wikipediafinder/backend/BFS.java b/backend/src/main/java/com/wikipediafinder/backend/BFS.java index c493d4d..c4f2db1 100644 --- a/backend/src/main/java/com/wikipediafinder/backend/BFS.java +++ b/backend/src/main/java/com/wikipediafinder/backend/BFS.java @@ -3,6 +3,7 @@ import com.wikipediafinder.backend.interfaces.BFSInterface; import java.util.*; import java.util.function.Function; +import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; /** @@ -32,6 +33,7 @@ public class BFS implements BFSInterface { * @throws IllegalArgumentException if {@code start} or {@code end} is null */ @Override + @Cacheable(value = "pathCache", key = "#start.URL + '->' + #end.URL") public List getPath(PageNode start, PageNode end) { return getPath(start, end, DEFAULT_FACTORY); } @@ -115,6 +117,7 @@ public List getPath( * @throws IllegalArgumentException if {@code start} or {@code end} is null */ @Override + @Cacheable(value = "pathStatsCache", key = "#start.URL + '->' + #end.URL") public BFSResult getPathWithStats(PageNode start, PageNode end) { return getPathWithStats(start, end, DEFAULT_FACTORY); } diff --git a/backend/src/main/java/com/wikipediafinder/backend/WikipediaFinderApplication.java b/backend/src/main/java/com/wikipediafinder/backend/WikipediaFinderApplication.java index 569aa6d..864d4f5 100644 --- a/backend/src/main/java/com/wikipediafinder/backend/WikipediaFinderApplication.java +++ b/backend/src/main/java/com/wikipediafinder/backend/WikipediaFinderApplication.java @@ -3,6 +3,7 @@ import com.wikipediafinder.backend.interfaces.WikipediaFinderApplicationInterface; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; @@ -10,6 +11,7 @@ /** Main Spring Boot application class for Wikipedia path finder. */ @SpringBootApplication +@EnableCaching public class WikipediaFinderApplication implements WikipediaFinderApplicationInterface { /** diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 4ab05d0..37d5625 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -1 +1,5 @@ server.port=${PORT:8080} + +# Cache configuration +spring.cache.cache-names=pathCache,pathStatsCache +spring.cache.caffeine.spec=maximumSize=1000,expireAfterWrite=1h diff --git a/backend/src/test/java/com/wikipediafinder/backend/CachingTest.java b/backend/src/test/java/com/wikipediafinder/backend/CachingTest.java new file mode 100644 index 0000000..7dae6dc --- /dev/null +++ b/backend/src/test/java/com/wikipediafinder/backend/CachingTest.java @@ -0,0 +1,106 @@ +package com.wikipediafinder.backend; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cache.CacheManager; + +/** + * Integration test to verify that caching is working correctly for BFS operations. Verifies that + * the cache manager is properly configured with the expected caches. + */ +@SpringBootTest +public class CachingTest { + + @Autowired private BFS bfs; + + @Autowired private CacheManager cacheManager; + + @Test + public void testCacheManagerIsConfigured() { + assertNotNull(cacheManager, "CacheManager should be configured"); + assertNotNull(cacheManager.getCache("pathCache"), "pathCache should exist"); + assertNotNull(cacheManager.getCache("pathStatsCache"), "pathStatsCache should exist"); + } + + @Test + public void testCachingWorksForGetPath() { + // Clear cache before test + cacheManager.getCache("pathCache").clear(); + + PageNode start = new PageNode("https://en.wikipedia.org/wiki/Test_A"); + PageNode end = new PageNode("https://en.wikipedia.org/wiki/Test_B"); + + // First call - cache miss, will compute and store + bfs.getPath(start, end); + + // Verify cache entry exists after first call + String cacheKey = start.getURL() + "->" + end.getURL(); + assertNotNull( + cacheManager.getCache("pathCache").get(cacheKey), + "Cache should contain entry for the path after first call"); + + // Second call - cache hit, should return cached result + bfs.getPath(start, end); + + // Verify cache still contains the entry + assertNotNull( + cacheManager.getCache("pathCache").get(cacheKey), + "Cache should still contain entry after second call"); + } + + @Test + public void testCachingWorksForGetPathWithStats() { + // Clear cache before test + cacheManager.getCache("pathStatsCache").clear(); + + PageNode start = new PageNode("https://en.wikipedia.org/wiki/Test_C"); + PageNode end = new PageNode("https://en.wikipedia.org/wiki/Test_D"); + + // First call - cache miss + BFSResult result1 = bfs.getPathWithStats(start, end); + + // Second call - should use cache + BFSResult result2 = bfs.getPathWithStats(start, end); + + // Verify cache entry exists + String cacheKey = start.getURL() + "->" + end.getURL(); + assertNotNull( + cacheManager.getCache("pathStatsCache").get(cacheKey), + "Cache should contain entry for the path with stats"); + + // Results should be equal (content comparison) + assertEquals(result1.getPath(), result2.getPath(), "Cached result path should match"); + assertEquals( + result1.getNodesExplored(), + result2.getNodesExplored(), + "Cached result nodes explored should match"); + } + + @Test + public void testCacheDifferentParameters() { + // Clear cache before test + cacheManager.getCache("pathCache").clear(); + + PageNode start1 = new PageNode("https://en.wikipedia.org/wiki/Test_E"); + PageNode end1 = new PageNode("https://en.wikipedia.org/wiki/Test_F"); + PageNode start2 = new PageNode("https://en.wikipedia.org/wiki/Test_G"); + PageNode end2 = new PageNode("https://en.wikipedia.org/wiki/Test_H"); + + // Make calls with different parameters + bfs.getPath(start1, end1); + bfs.getPath(start2, end2); + + // Verify both entries are cached separately + String cacheKey1 = start1.getURL() + "->" + end1.getURL(); + String cacheKey2 = start2.getURL() + "->" + end2.getURL(); + + assertNotNull( + cacheManager.getCache("pathCache").get(cacheKey1), "Cache should contain first path entry"); + assertNotNull( + cacheManager.getCache("pathCache").get(cacheKey2), + "Cache should contain second path entry"); + } +}