Skip to content

Commit 5a3c5c8

Browse files
committed
updated: ai-recommendation-service
1 parent 9ec7f7c commit 5a3c5c8

2 files changed

Lines changed: 130 additions & 0 deletions

File tree

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package com.lakeserl.ai_recommendation_service.service;
2+
3+
import com.lakeserl.ai_recommendation_service.client.ProductServiceClient;
4+
import com.lakeserl.ai_recommendation_service.client.UserServiceClient;
5+
import org.junit.jupiter.api.BeforeEach;
6+
import org.junit.jupiter.api.Test;
7+
import org.springframework.data.redis.core.RedisTemplate;
8+
import org.springframework.test.util.ReflectionTestUtils;
9+
10+
import java.util.Set;
11+
12+
import static org.assertj.core.api.Assertions.assertThat;
13+
import static org.assertj.core.api.Assertions.within;
14+
import static org.mockito.Mockito.mock;
15+
16+
/**
17+
* Content-based "similar" ranking relies on Jaccard similarity over product attribute sets.
18+
* The ranking is only meaningful if identical sets score 1.0, disjoint sets 0.0, and partial
19+
* overlap exactly intersection/union — otherwise "similar" products are mis-ordered.
20+
*/
21+
class RecommendationServiceImplTest {
22+
23+
private RecommendationServiceImpl service;
24+
25+
@BeforeEach
26+
@SuppressWarnings("unchecked")
27+
void setUp() {
28+
service = new RecommendationServiceImpl(
29+
mock(ProductServiceClient.class), mock(UserServiceClient.class), mock(RedisTemplate.class));
30+
}
31+
32+
private double jaccard(Set<String> a, Set<String> b) {
33+
Double v = ReflectionTestUtils.invokeMethod(service, "jaccardSimilarity", a, b);
34+
return v == null ? Double.NaN : v;
35+
}
36+
37+
@Test
38+
void identicalAttributeSetsScoreOne() {
39+
assertThat(jaccard(Set.of("OILY", "ACNE"), Set.of("OILY", "ACNE"))).isEqualTo(1.0);
40+
}
41+
42+
@Test
43+
void disjointAttributeSetsScoreZero() {
44+
assertThat(jaccard(Set.of("OILY"), Set.of("DRY"))).isEqualTo(0.0);
45+
}
46+
47+
@Test
48+
void partialOverlapScoresIntersectionOverUnion() {
49+
// {A,B,C} vs {B,C,D}: intersection {B,C}=2, union {A,B,C,D}=4 -> 0.5
50+
assertThat(jaccard(Set.of("A", "B", "C"), Set.of("B", "C", "D"))).isCloseTo(0.5, within(1e-9));
51+
}
52+
53+
@Test
54+
void twoEmptySetsScoreOne() {
55+
assertThat(jaccard(Set.of(), Set.of())).isEqualTo(1.0);
56+
}
57+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
server:
2+
port: 8096
3+
4+
spring:
5+
application:
6+
name: ai-recommendation-service
7+
config:
8+
import:
9+
- optional:file:.env[.properties]
10+
- optional:file:../.env[.properties]
11+
12+
data:
13+
redis:
14+
host: ${SPRING_DATA_REDIS_HOST:localhost}
15+
port: 6379
16+
17+
kafka:
18+
bootstrap-servers: ${SPRING_KAFKA_BOOTSTRAP_SERVERS:localhost:9092}
19+
consumer:
20+
group-id: recommendation-service-group
21+
auto-offset-reset: earliest
22+
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
23+
value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
24+
25+
eureka:
26+
client:
27+
serviceUrl:
28+
defaultZone: ${EUREKA_URI:http://localhost:8761/eureka}
29+
register-with-eureka: true
30+
fetch-registry: true
31+
instance:
32+
prefer-ip-address: true
33+
34+
management:
35+
tracing:
36+
sampling:
37+
probability: ${TRACING_SAMPLE_RATE:0.1}
38+
zipkin:
39+
tracing:
40+
endpoint: http://${ZIPKIN_HOST:localhost}:9411/api/v2/spans
41+
endpoints:
42+
web:
43+
exposure:
44+
include: health,info,metrics,prometheus
45+
endpoint:
46+
health:
47+
show-details: always
48+
probes:
49+
enabled: true
50+
health:
51+
livenessstate:
52+
enabled: true
53+
readinessstate:
54+
enabled: true
55+
metrics:
56+
tags:
57+
application: ${spring.application.name}
58+
environment: ${ENVIRONMENT:dev}
59+
60+
springdoc:
61+
swagger-ui:
62+
path: /docs
63+
64+
app:
65+
internal:
66+
secret: ${INTERNAL_SECRET}
67+
68+
logging:
69+
pattern:
70+
level: "%5p [${spring.application.name},%X{traceId:-},%X{spanId:-}]"
71+
level:
72+
root: INFO
73+
com.lakeserl.ai_recommendation_service: INFO

0 commit comments

Comments
 (0)