diff --git a/postify/pom.xml b/postify/pom.xml index 7cea5f7..a02f898 100644 --- a/postify/pom.xml +++ b/postify/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 4.0.6 + 3.2.5 net.hackyourfuture.backend.week6 @@ -27,14 +27,28 @@ - 25 + 21 + + + + org.testcontainers + testcontainers-bom + 1.19.7 + pom + import + + + org.springframework.boot - spring-boot-starter-webmvc + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-jdbc - org.postgresql postgresql @@ -45,11 +59,22 @@ lombok true + + org.testcontainers + junit-jupiter + test + + + org.testcontainers + postgresql + test + org.springframework.boot - spring-boot-starter-webmvc-test + spring-boot-starter-test test + diff --git a/postify/src/main/java/net/hackyourfuture/backend/week6/postify/DBConnectionCheck.java b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/DBConnectionCheck.java new file mode 100644 index 0000000..89c3b4d --- /dev/null +++ b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/DBConnectionCheck.java @@ -0,0 +1,20 @@ +package net.hackyourfuture.backend.week6.postify; + +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.event.EventListener; +import org.springframework.jdbc.core.JdbcTemplate; + +public class DBConnectionCheck { + + private final JdbcTemplate jdbcTemplate; + + public DBConnectionCheck(JdbcTemplate jdbcTemplate){ + this.jdbcTemplate = jdbcTemplate; + } + + @EventListener(ApplicationReadyEvent.class) + public void checkConnection(){ + String result = this.jdbcTemplate.queryForObject("SELECT 'connected'", String.class); + System.out.println("Database status: " + result); + } +} diff --git a/postify/src/main/java/net/hackyourfuture/backend/week6/postify/client/LyricsApiConfiguration.java b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/client/LyricsApiConfiguration.java new file mode 100644 index 0000000..5a71043 --- /dev/null +++ b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/client/LyricsApiConfiguration.java @@ -0,0 +1,23 @@ +package net.hackyourfuture.backend.week6.postify.client; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.web.client.RestClient; + +@Configuration +public class LyricsApiConfiguration { + + @Bean + public RestClient.Builder restClientBuilder() { + return RestClient.builder(); + } + + @Bean + public RestClient lyricsRestClient(RestClient.Builder builder){ + return builder.baseUrl("https://api.lyrics.ovh") + .defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE) + .build(); + } +} diff --git a/postify/src/main/java/net/hackyourfuture/backend/week6/postify/client/LyricsClient.java b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/client/LyricsClient.java new file mode 100644 index 0000000..5ebdba0 --- /dev/null +++ b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/client/LyricsClient.java @@ -0,0 +1,30 @@ +package net.hackyourfuture.backend.week6.postify.client; + +import net.hackyourfuture.backend.week6.postify.dto.response.lyric.LyricApiResponse; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Service; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestClient; + +@Service +public class LyricsClient { + private final RestClient lyricsRestClient; + + public LyricsClient(@Qualifier("lyricsRestClient") RestClient lyricsRestClient){ + this.lyricsRestClient = lyricsRestClient; + } + + public LyricApiResponse getLyrics(String artistName, String trackTitle){ + try { + return lyricsRestClient.get() + .uri(uriBuilder -> uriBuilder + .path("/v1/{artist}/{title}") + .build(artistName, trackTitle)) + .retrieve() + .body(LyricApiResponse.class); + } catch(HttpClientErrorException.NotFound ex){ + return null; + } + + } +} diff --git a/postify/src/main/java/net/hackyourfuture/backend/week6/postify/controllers/TrackLyricsController.java b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/controllers/TrackLyricsController.java new file mode 100644 index 0000000..23e2c52 --- /dev/null +++ b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/controllers/TrackLyricsController.java @@ -0,0 +1,23 @@ +package net.hackyourfuture.backend.week6.postify.controllers; + +import net.hackyourfuture.backend.week6.postify.dto.response.lyric.TrackLyricResponse; +import net.hackyourfuture.backend.week6.postify.services.TrackLyricsService; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/tracks") +public class TrackLyricsController { + private final TrackLyricsService service; + + public TrackLyricsController(TrackLyricsService service){ + this.service = service; + } + + @GetMapping("/{id}/lyrics") + public TrackLyricResponse getLyrics(@PathVariable("id") int trackId){ + return service.getLyricsForTrack(trackId); + } +} diff --git a/postify/src/main/java/net/hackyourfuture/backend/week6/postify/controllers/UserStatisticsController.java b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/controllers/UserStatisticsController.java new file mode 100644 index 0000000..72397a2 --- /dev/null +++ b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/controllers/UserStatisticsController.java @@ -0,0 +1,24 @@ +package net.hackyourfuture.backend.week6.postify.controllers; + +import net.hackyourfuture.backend.week6.postify.dto.response.user.UserStatisticsResponse; +import net.hackyourfuture.backend.week6.postify.services.UserStatisticsService; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/users") +public class UserStatisticsController { + + private final UserStatisticsService service; + + public UserStatisticsController(UserStatisticsService service){ + this.service = service; + } + + @GetMapping("/{id}/stats") + public UserStatisticsResponse getUserStats(@PathVariable int id){ + return service.getStatisticsForUser(id); + } +} diff --git a/postify/src/main/java/net/hackyourfuture/backend/week6/postify/dto/response/lyric/LyricApiResponse.java b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/dto/response/lyric/LyricApiResponse.java new file mode 100644 index 0000000..96b18d9 --- /dev/null +++ b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/dto/response/lyric/LyricApiResponse.java @@ -0,0 +1,4 @@ +package net.hackyourfuture.backend.week6.postify.dto.response.lyric; + +public record LyricApiResponse(String lyrics) { +} diff --git a/postify/src/main/java/net/hackyourfuture/backend/week6/postify/dto/response/lyric/TrackBasicInfo.java b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/dto/response/lyric/TrackBasicInfo.java new file mode 100644 index 0000000..b017be3 --- /dev/null +++ b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/dto/response/lyric/TrackBasicInfo.java @@ -0,0 +1,4 @@ +package net.hackyourfuture.backend.week6.postify.dto.response.lyric; + +public record TrackBasicInfo (int trackId, String trackTitle, String artistName){ +} diff --git a/postify/src/main/java/net/hackyourfuture/backend/week6/postify/dto/response/lyric/TrackLyricResponse.java b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/dto/response/lyric/TrackLyricResponse.java new file mode 100644 index 0000000..8d3f0ef --- /dev/null +++ b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/dto/response/lyric/TrackLyricResponse.java @@ -0,0 +1,6 @@ +package net.hackyourfuture.backend.week6.postify.dto.response.lyric; + +import java.util.List; + +public record TrackLyricResponse (int trackId, String trackTitle, String artistName, List lyrics){ +} diff --git a/postify/src/main/java/net/hackyourfuture/backend/week6/postify/dto/response/user/UserBasicInfoResponse.java b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/dto/response/user/UserBasicInfoResponse.java new file mode 100644 index 0000000..21d1c45 --- /dev/null +++ b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/dto/response/user/UserBasicInfoResponse.java @@ -0,0 +1,4 @@ +package net.hackyourfuture.backend.week6.postify.dto.response.user; + +public record UserBasicInfoResponse(int id, String name, String country) { +} diff --git a/postify/src/main/java/net/hackyourfuture/backend/week6/postify/dto/response/user/UserStatisticsResponse.java b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/dto/response/user/UserStatisticsResponse.java new file mode 100644 index 0000000..dfb3cc1 --- /dev/null +++ b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/dto/response/user/UserStatisticsResponse.java @@ -0,0 +1,13 @@ +package net.hackyourfuture.backend.week6.postify.dto.response.user; + +public record UserStatisticsResponse( + int userId, + String userName, + String userCountry, + int totalStreams, + int uniqueTracksStreamed, + int uniqueArtistsStreamed, + String favoriteGenre, + int totalListeningTimeSeconds + ) { +} diff --git a/postify/src/main/java/net/hackyourfuture/backend/week6/postify/exceptions/GlobalExceptionHandler.java b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/exceptions/GlobalExceptionHandler.java new file mode 100644 index 0000000..f7c0118 --- /dev/null +++ b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/exceptions/GlobalExceptionHandler.java @@ -0,0 +1,25 @@ +package net.hackyourfuture.backend.week6.postify.exceptions; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(UserNotFoundException.class) + public ResponseEntity handleUserNotFound(UserNotFoundException ex){ + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage()); + } + + @ExceptionHandler(TrackNotFoundException.class) + public ResponseEntity handleTrackNotFound(TrackNotFoundException ex){ + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage()); + } + + @ExceptionHandler(LyricsNotFoundException.class) + public ResponseEntity handleLyricsNotFound(LyricsNotFoundException ex){ + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage()); + } +} diff --git a/postify/src/main/java/net/hackyourfuture/backend/week6/postify/exceptions/LyricsNotFoundException.java b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/exceptions/LyricsNotFoundException.java new file mode 100644 index 0000000..eaec387 --- /dev/null +++ b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/exceptions/LyricsNotFoundException.java @@ -0,0 +1,7 @@ +package net.hackyourfuture.backend.week6.postify.exceptions; + +public class LyricsNotFoundException extends RuntimeException{ + public LyricsNotFoundException(String trackTitle, String artistName){ + super("Lyrics not found for track '" + trackTitle + "' by '" + artistName + "'"); + } +} diff --git a/postify/src/main/java/net/hackyourfuture/backend/week6/postify/exceptions/TrackNotFoundException.java b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/exceptions/TrackNotFoundException.java new file mode 100644 index 0000000..2dbf1e3 --- /dev/null +++ b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/exceptions/TrackNotFoundException.java @@ -0,0 +1,7 @@ +package net.hackyourfuture.backend.week6.postify.exceptions; + +public class TrackNotFoundException extends RuntimeException{ + public TrackNotFoundException(int id){ + super("Track with id " + id + " not found"); + } +} diff --git a/postify/src/main/java/net/hackyourfuture/backend/week6/postify/exceptions/UserNotFoundException.java b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/exceptions/UserNotFoundException.java new file mode 100644 index 0000000..b44add9 --- /dev/null +++ b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/exceptions/UserNotFoundException.java @@ -0,0 +1,7 @@ +package net.hackyourfuture.backend.week6.postify.exceptions; + +public class UserNotFoundException extends RuntimeException{ + public UserNotFoundException(int id){ + super("User with id " + id + " not found"); + } +} diff --git a/postify/src/main/java/net/hackyourfuture/backend/week6/postify/repository/TracksLyricsRepository.java b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/repository/TracksLyricsRepository.java new file mode 100644 index 0000000..0464caf --- /dev/null +++ b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/repository/TracksLyricsRepository.java @@ -0,0 +1,36 @@ +package net.hackyourfuture.backend.week6.postify.repository; + +import net.hackyourfuture.backend.week6.postify.dto.response.lyric.TrackBasicInfo; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public class TracksLyricsRepository { + private final JdbcTemplate jdbcTemplate; + + public TracksLyricsRepository(JdbcTemplate jdbcTemplate){ + this.jdbcTemplate = jdbcTemplate; + } + + public Optional findTrackWithArtist(int trackId){ + String sql = """ + SELECT t.track_id, t.track_title, a.artist_name + FROM tracks t + JOIN albums al ON t.album_id = al.album_id + JOIN artists a ON al.artist_id = a.artist_id + WHERE t.track_id = ? + """; + + return jdbcTemplate.query(sql,rs -> { + if(rs.next()){ + return Optional.of(new TrackBasicInfo( + rs.getInt("track_id"), + rs.getString("track_title"), + rs.getString("artist_name") + )); + } return Optional.empty(); + }, trackId); + } +} diff --git a/postify/src/main/java/net/hackyourfuture/backend/week6/postify/repository/UserStatisticsRepository.java b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/repository/UserStatisticsRepository.java new file mode 100644 index 0000000..e14c6eb --- /dev/null +++ b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/repository/UserStatisticsRepository.java @@ -0,0 +1,130 @@ +package net.hackyourfuture.backend.week6.postify.repository; + +import net.hackyourfuture.backend.week6.postify.dto.response.user.UserBasicInfoResponse; +import net.hackyourfuture.backend.week6.postify.dto.response.user.UserStatisticsResponse; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public class UserStatisticsRepository { + + private final JdbcTemplate jdbcTemplate; + + public UserStatisticsRepository(JdbcTemplate jdbcTemplate){ + this.jdbcTemplate = jdbcTemplate; + } + + public boolean userExists(int userId){ + String sql = """ + SELECT COUNT(*) + FROM users + WHERE user_id = ? + """; + + Integer count = jdbcTemplate.queryForObject(sql, Integer.class, userId); + return count !=null && count>0; + } + + public Optional fetchUserBasic(int userId){ + String sql = """ + SELECT user_id, user_name, user_country + FROM users + WHERE user_id = ? + """; + + return jdbcTemplate.query(sql, rs -> { + if(rs.next()){ + return Optional.of(new UserBasicInfoResponse( + rs.getInt("user_id"), + rs.getString("user_name"), + rs.getString("user_country") + )); + } + return Optional.empty(); + }, userId); + } + + public int fetchTotalStreams(int userId){ + String sql = """ + SELECT COUNT(*) + FROM streams + WHERE user_id = ? + """; + return jdbcTemplate.queryForObject(sql, Integer.class , userId); + } + + private int fetchUniqueTracks(int userId){ + String sql = """ + SELECT COUNT(DISTINCT track_id) + FROM streams + WHERE user_id = ? + """; + + return jdbcTemplate.queryForObject(sql, Integer.class, userId); + } + + private int fetchUniqueArtists(int userId){ + String sql = """ + SELECT COUNT(DISTINCT a.artist_id) + FROM streams s + JOIN tracks t ON s.track_id = t.track_id + JOIN albums al ON t.album_id = al.album_id + JOIN artists a ON al.artist_id = a.artist_id + WHERE s.user_id = ? + """; + return jdbcTemplate.queryForObject(sql, Integer.class, userId); + } + + private String fetchFavoriteGenre(int userId){ + String sql = """ + SELECT t.genre + FROM streams s + JOIN tracks t ON s.track_id = t.track_id + WHERE user_id = ? + GROUP BY t.genre + ORDER BY COUNT(*) DESC + LIMIT 1 + """; + return jdbcTemplate.query(sql, + rs -> rs.next() ? rs.getString("genre") : null, + userId); + } + + private int fetchTotalListeningTime(int userId){ + String sql = """ + SELECT COALESCE(SUM(t.track_duration_s), 0) + FROM streams s + JOIN tracks t ON s.track_id = t.track_id + WHERE s.user_id = ? + """; + return jdbcTemplate.queryForObject(sql, Integer.class, userId); + } + + public Optional fetchStatistics(int userId){ + + Optional basic = fetchUserBasic(userId); + + if(basic.isEmpty()) return Optional.empty(); + + int totalStreams = fetchTotalStreams(userId); + int uniqueTracks = fetchUniqueTracks(userId); + int uniqueArtists = fetchUniqueArtists(userId); + String favoriteGenre = fetchFavoriteGenre(userId); + int totalListeningTime = fetchTotalListeningTime(userId); + + UserBasicInfoResponse user = basic.get(); + + return Optional.of(new UserStatisticsResponse( + user.id(), + user.name(), + user.country(), + totalStreams, + uniqueTracks, + uniqueArtists, + favoriteGenre, + totalListeningTime + )); + } +} diff --git a/postify/src/main/java/net/hackyourfuture/backend/week6/postify/services/TrackLyricsService.java b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/services/TrackLyricsService.java new file mode 100644 index 0000000..a2753eb --- /dev/null +++ b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/services/TrackLyricsService.java @@ -0,0 +1,45 @@ +package net.hackyourfuture.backend.week6.postify.services; + +import net.hackyourfuture.backend.week6.postify.client.LyricsClient; +import net.hackyourfuture.backend.week6.postify.dto.response.lyric.LyricApiResponse; +import net.hackyourfuture.backend.week6.postify.dto.response.lyric.TrackBasicInfo; +import net.hackyourfuture.backend.week6.postify.dto.response.lyric.TrackLyricResponse; +import net.hackyourfuture.backend.week6.postify.exceptions.LyricsNotFoundException; +import net.hackyourfuture.backend.week6.postify.exceptions.TrackNotFoundException; +import net.hackyourfuture.backend.week6.postify.repository.TracksLyricsRepository; +import org.springframework.stereotype.Service; + +import java.util.Arrays; +import java.util.List; + +@Service +public class TrackLyricsService { + private final TracksLyricsRepository repository; + private final LyricsClient lyricsClient; + + public TrackLyricsService(TracksLyricsRepository repository, LyricsClient lyricsClient){ + this.repository = repository; + this.lyricsClient = lyricsClient; + } + + public TrackLyricResponse getLyricsForTrack(int trackId){ + + TrackBasicInfo track = repository.findTrackWithArtist(trackId) + .orElseThrow(() -> new TrackNotFoundException(trackId)); + + LyricApiResponse apiResponse = lyricsClient.getLyrics(track.artistName(), track.trackTitle()); + + if (apiResponse == null || apiResponse.lyrics() == null || apiResponse.lyrics().isBlank()){ + throw new LyricsNotFoundException(track.trackTitle(), track.artistName()); + } + + List lines = Arrays.asList(apiResponse.lyrics().split("\n")); + + return new TrackLyricResponse( + track.trackId(), + track.trackTitle(), + track.artistName(), + lines + ); + } +} diff --git a/postify/src/main/java/net/hackyourfuture/backend/week6/postify/services/UserStatisticsService.java b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/services/UserStatisticsService.java new file mode 100644 index 0000000..f1f7d2e --- /dev/null +++ b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/services/UserStatisticsService.java @@ -0,0 +1,25 @@ +package net.hackyourfuture.backend.week6.postify.services; + +import net.hackyourfuture.backend.week6.postify.dto.response.user.UserStatisticsResponse; +import net.hackyourfuture.backend.week6.postify.exceptions.UserNotFoundException; +import net.hackyourfuture.backend.week6.postify.repository.UserStatisticsRepository; +import org.springframework.stereotype.Service; + +@Service +public class UserStatisticsService { + + private final UserStatisticsRepository repository; + + public UserStatisticsService(UserStatisticsRepository repository){ + this.repository = repository; + } + + public UserStatisticsResponse getStatisticsForUser(int userId) { + if (!repository.userExists(userId)) { + throw new UserNotFoundException(userId); + } + + return repository.fetchStatistics(userId).orElseThrow(() -> new UserNotFoundException(userId)); + } +} + diff --git a/postify/src/main/resources/application.properties b/postify/src/main/resources/application.properties index 8f4842f..6792daf 100644 --- a/postify/src/main/resources/application.properties +++ b/postify/src/main/resources/application.properties @@ -1 +1,4 @@ -spring.application.name=postify +spring.datasource.url=jdbc:postgresql://localhost:5432/postify +spring.datasource.username=postgres +spring.datasource.password=postgres +spring.datasource.driver-class-name=org.postgresql.Driver diff --git a/postify/src/test/java/net/hackyourfuture/backend/week6/postify/PostifyApplicationTests.java b/postify/src/test/java/net/hackyourfuture/backend/week6/postify/PostifyApplicationTests.java deleted file mode 100644 index f5ec046..0000000 --- a/postify/src/test/java/net/hackyourfuture/backend/week6/postify/PostifyApplicationTests.java +++ /dev/null @@ -1,13 +0,0 @@ -package net.hackyourfuture.backend.week6.postify; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class PostifyApplicationTests { - - @Test - void contextLoads() { - } - -} diff --git a/postify/src/test/java/net/hackyourfuture/backend/week6/postify/controllers/TrackLyricsControllerTest.java b/postify/src/test/java/net/hackyourfuture/backend/week6/postify/controllers/TrackLyricsControllerTest.java new file mode 100644 index 0000000..17d64d6 --- /dev/null +++ b/postify/src/test/java/net/hackyourfuture/backend/week6/postify/controllers/TrackLyricsControllerTest.java @@ -0,0 +1,63 @@ +package net.hackyourfuture.backend.week6.postify.controllers; + +import net.hackyourfuture.backend.week6.postify.client.LyricsClient; +import net.hackyourfuture.backend.week6.postify.dto.response.lyric.LyricApiResponse; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.web.servlet.MockMvc; + +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@SpringBootTest +@AutoConfigureMockMvc +class TrackLyricsControllerTest{ + + @Autowired + private MockMvc mockMvc; + + @MockBean + private LyricsClient lyricsClient; + + @Test + void getLyrics_existingTrack_returnsLyrics() throws Exception{ + + when(lyricsClient.getLyrics("Billie Eilish", "LUNCH")) + .thenReturn(new LyricApiResponse(""" + Line 1 + Line 2 + """)); + + mockMvc.perform(get("/tracks/41/lyrics")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.trackId").value(41)) + .andExpect(jsonPath("$.trackTitle").value("LUNCH")) + .andExpect(jsonPath("$.artistName").value("Billie Eilish")) + .andExpect(jsonPath("$.lyrics[0]").value("Line 1")) + .andExpect(jsonPath("$.lyrics[1]").value("Line 2")); + + + } + + @Test + void getLyrics_nonExistingTrack_returns404() throws Exception { + mockMvc.perform(get("/tracks/9999/lyrics")) + .andExpect(status().isNotFound()) + .andExpect(content().string("Track with id 9999 not found")); + } + + @Test + void getLyrics_existingTrackButNoLyrics_returns404() throws Exception { + + when(lyricsClient.getLyrics("Billie Eilish", "LUNCH")) + .thenReturn(new LyricApiResponse("")); + + mockMvc.perform(get("/tracks/41/lyrics")) + .andExpect(status().isNotFound()) + .andExpect(content().string( "Lyrics not found for track 'LUNCH' by 'Billie Eilish'")); + } +} diff --git a/postify/src/test/java/net/hackyourfuture/backend/week6/postify/controllers/UserStatisticsControllerTest.java b/postify/src/test/java/net/hackyourfuture/backend/week6/postify/controllers/UserStatisticsControllerTest.java new file mode 100644 index 0000000..984dc77 --- /dev/null +++ b/postify/src/test/java/net/hackyourfuture/backend/week6/postify/controllers/UserStatisticsControllerTest.java @@ -0,0 +1,38 @@ +package net.hackyourfuture.backend.week6.postify.controllers; + + + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.web.servlet.MockMvc; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@SpringBootTest +@AutoConfigureMockMvc +class UserStatisticsControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Test + void getUserStats_existingUser_returnsOkAndBody() throws Exception{ + mockMvc.perform(get("/users/1/stats")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.userId").value(1)) + .andExpect(jsonPath("$.userName").value("lena_v")) + .andExpect(jsonPath("$.userCountry").value("NL")) + .andExpect(jsonPath("$.totalStreams").isNumber()); + } + + @Test + void getUserStats_nonExistingUser_returns404() throws Exception { + mockMvc.perform(get("/users/9999/stats")) + .andExpect(status().isNotFound()) + .andExpect(content().string("User with id 9999 not found")); + + } +} \ No newline at end of file