Unify formatting

This commit is contained in:
2026-01-05 15:50:23 +01:00
parent 7aa3f96b3f
commit 864aeb86eb
73 changed files with 6436 additions and 6047 deletions

View File

@@ -1,35 +1,35 @@
package dev.kruhlmann.imgfloat;
import com.fasterxml.jackson.databind.ObjectMapper;
import dev.kruhlmann.imgfloat.model.VisibilityRequest;
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.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.mock.web.MockMultipartFile;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import javax.imageio.ImageIO;
import static org.hamcrest.Matchers.hasSize;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.oauth2Login;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest(properties = {
import com.fasterxml.jackson.databind.ObjectMapper;
import dev.kruhlmann.imgfloat.model.VisibilityRequest;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import javax.imageio.ImageIO;
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.http.MediaType;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.web.servlet.MockMvc;
@SpringBootTest(
properties = {
"spring.security.oauth2.client.registration.twitch.client-id=test-client-id",
"spring.security.oauth2.client.registration.twitch.client-secret=test-client-secret"
})
"spring.security.oauth2.client.registration.twitch.client-secret=test-client-secret",
}
)
@AutoConfigureMockMvc
class ChannelApiIntegrationTest {
@@ -42,57 +42,92 @@ class ChannelApiIntegrationTest {
@Test
void broadcasterManagesAdminsAndAssets() throws Exception {
String broadcaster = "caster";
mockMvc.perform(post("/api/channels/{broadcaster}/admins", broadcaster)
.contentType(MediaType.APPLICATION_JSON)
.content("{\"username\":\"helper\"}")
.with(oauth2Login().attributes(attrs -> attrs.put("preferred_username", broadcaster))))
.andExpect(status().isOk());
mockMvc
.perform(
post("/api/channels/{broadcaster}/admins", broadcaster)
.contentType(MediaType.APPLICATION_JSON)
.content("{\"username\":\"helper\"}")
.with(oauth2Login().attributes((attrs) -> attrs.put("preferred_username", broadcaster)))
)
.andExpect(status().isOk());
mockMvc.perform(get("/api/channels/{broadcaster}/admins", broadcaster)
.with(oauth2Login().attributes(attrs -> attrs.put("preferred_username", broadcaster))))
.andExpect(status().isOk())
.andExpect(jsonPath("$[0].login").value("helper"))
.andExpect(jsonPath("$[0].displayName").value("helper"));
mockMvc
.perform(
get("/api/channels/{broadcaster}/admins", broadcaster).with(
oauth2Login().attributes((attrs) -> attrs.put("preferred_username", broadcaster))
)
)
.andExpect(status().isOk())
.andExpect(jsonPath("$[0].login").value("helper"))
.andExpect(jsonPath("$[0].displayName").value("helper"));
MockMultipartFile file = new MockMultipartFile("file", "image.png", "image/png", samplePng());
String assetId = objectMapper.readTree(mockMvc.perform(multipart("/api/channels/{broadcaster}/assets", broadcaster)
.file(file)
.with(oauth2Login().attributes(attrs -> attrs.put("preferred_username", broadcaster))))
.andExpect(status().isOk())
.andReturn().getResponse().getContentAsString()).get("id").asText();
String assetId = objectMapper
.readTree(
mockMvc
.perform(
multipart("/api/channels/{broadcaster}/assets", broadcaster)
.file(file)
.with(oauth2Login().attributes((attrs) -> attrs.put("preferred_username", broadcaster)))
)
.andExpect(status().isOk())
.andReturn()
.getResponse()
.getContentAsString()
)
.get("id")
.asText();
mockMvc.perform(get("/api/channels/{broadcaster}/assets", broadcaster)
.with(oauth2Login().attributes(attrs -> attrs.put("preferred_username", broadcaster))))
.andExpect(status().isOk())
.andExpect(jsonPath("$", hasSize(1)));
mockMvc
.perform(
get("/api/channels/{broadcaster}/assets", broadcaster).with(
oauth2Login().attributes((attrs) -> attrs.put("preferred_username", broadcaster))
)
)
.andExpect(status().isOk())
.andExpect(jsonPath("$", hasSize(1)));
VisibilityRequest visibilityRequest = new VisibilityRequest();
visibilityRequest.setHidden(false);
mockMvc.perform(put("/api/channels/{broadcaster}/assets/{id}/visibility", broadcaster, assetId)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(visibilityRequest))
.with(oauth2Login().attributes(attrs -> attrs.put("preferred_username", broadcaster))))
.andExpect(status().isOk())
.andExpect(jsonPath("$.hidden").value(false));
mockMvc
.perform(
put("/api/channels/{broadcaster}/assets/{id}/visibility", broadcaster, assetId)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(visibilityRequest))
.with(oauth2Login().attributes((attrs) -> attrs.put("preferred_username", broadcaster)))
)
.andExpect(status().isOk())
.andExpect(jsonPath("$.hidden").value(false));
mockMvc.perform(get("/api/channels/{broadcaster}/assets/visible", broadcaster)
.with(oauth2Login().attributes(attrs -> attrs.put("preferred_username", broadcaster))))
.andExpect(status().isOk())
.andExpect(jsonPath("$", hasSize(1)));
mockMvc
.perform(
get("/api/channels/{broadcaster}/assets/visible", broadcaster).with(
oauth2Login().attributes((attrs) -> attrs.put("preferred_username", broadcaster))
)
)
.andExpect(status().isOk())
.andExpect(jsonPath("$", hasSize(1)));
mockMvc.perform(delete("/api/channels/{broadcaster}/assets/{id}", broadcaster, assetId)
.with(oauth2Login().attributes(attrs -> attrs.put("preferred_username", broadcaster))))
.andExpect(status().isOk());
mockMvc
.perform(
delete("/api/channels/{broadcaster}/assets/{id}", broadcaster, assetId).with(
oauth2Login().attributes((attrs) -> attrs.put("preferred_username", broadcaster))
)
)
.andExpect(status().isOk());
}
@Test
void rejectsAdminChangesFromNonBroadcaster() throws Exception {
mockMvc.perform(post("/api/channels/{broadcaster}/admins", "caster")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"username\":\"helper\"}")
.with(oauth2Login().attributes(attrs -> attrs.put("preferred_username", "intruder"))))
.andExpect(status().isForbidden());
mockMvc
.perform(
post("/api/channels/{broadcaster}/admins", "caster")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"username\":\"helper\"}")
.with(oauth2Login().attributes((attrs) -> attrs.put("preferred_username", "intruder")))
)
.andExpect(status().isForbidden());
}
private byte[] samplePng() throws IOException {

View File

@@ -1,5 +1,10 @@
package dev.kruhlmann.imgfloat;
import static org.hamcrest.Matchers.hasSize;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import dev.kruhlmann.imgfloat.model.Channel;
import dev.kruhlmann.imgfloat.repository.ChannelRepository;
import org.junit.jupiter.api.BeforeEach;
@@ -9,15 +14,12 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMock
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
import static org.hamcrest.Matchers.hasSize;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest(properties = {
@SpringBootTest(
properties = {
"spring.security.oauth2.client.registration.twitch.client-id=test-client-id",
"spring.security.oauth2.client.registration.twitch.client-secret=test-client-secret"
})
"spring.security.oauth2.client.registration.twitch.client-secret=test-client-secret",
}
)
@AutoConfigureMockMvc
class ChannelDirectoryApiIntegrationTest {
@@ -38,10 +40,11 @@ class ChannelDirectoryApiIntegrationTest {
channelRepository.save(new Channel("alpha"));
channelRepository.save(new Channel("ALPINE"));
mockMvc.perform(get("/api/channels").param("q", "Al"))
.andExpect(status().isOk())
.andExpect(jsonPath("$", hasSize(2)))
.andExpect(jsonPath("$[0]").value("alpha"))
.andExpect(jsonPath("$[1]").value("alpine"));
mockMvc
.perform(get("/api/channels").param("q", "Al"))
.andExpect(status().isOk())
.andExpect(jsonPath("$", hasSize(2)))
.andExpect(jsonPath("$[0]").value("alpha"))
.andExpect(jsonPath("$[1]").value("alpine"));
}
}

View File

@@ -1,27 +1,28 @@
package dev.kruhlmann.imgfloat;
import dev.kruhlmann.imgfloat.model.TransformRequest;
import dev.kruhlmann.imgfloat.model.VisibilityRequest;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import dev.kruhlmann.imgfloat.model.Asset;
import dev.kruhlmann.imgfloat.model.AssetView;
import dev.kruhlmann.imgfloat.model.Channel;
import dev.kruhlmann.imgfloat.model.Settings;
import dev.kruhlmann.imgfloat.model.TransformRequest;
import dev.kruhlmann.imgfloat.model.VisibilityRequest;
import dev.kruhlmann.imgfloat.repository.AssetRepository;
import dev.kruhlmann.imgfloat.repository.ChannelRepository;
import dev.kruhlmann.imgfloat.service.ChannelDirectoryService;
import dev.kruhlmann.imgfloat.service.AssetStorageService;
import dev.kruhlmann.imgfloat.service.ChannelDirectoryService;
import dev.kruhlmann.imgfloat.service.SettingsService;
import dev.kruhlmann.imgfloat.service.media.MediaDetectionService;
import dev.kruhlmann.imgfloat.service.media.MediaOptimizationService;
import dev.kruhlmann.imgfloat.service.media.MediaPreviewService;
import dev.kruhlmann.imgfloat.service.SettingsService;
import dev.kruhlmann.imgfloat.model.Settings;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.server.ResponseStatusException;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -33,19 +34,17 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import javax.imageio.ImageIO;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.verify;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.server.ResponseStatusException;
class ChannelDirectoryServiceTest {
private ChannelDirectoryService service;
private SimpMessagingTemplate messagingTemplate;
private ChannelRepository channelRepository;
@@ -66,8 +65,15 @@ class ChannelDirectoryServiceTest {
MediaPreviewService mediaPreviewService = new MediaPreviewService();
MediaOptimizationService mediaOptimizationService = new MediaOptimizationService(mediaPreviewService);
MediaDetectionService mediaDetectionService = new MediaDetectionService();
service = new ChannelDirectoryService(channelRepository, assetRepository, messagingTemplate,
assetStorageService, mediaDetectionService, mediaOptimizationService, settingsService);
service = new ChannelDirectoryService(
channelRepository,
assetRepository,
messagingTemplate,
assetStorageService,
mediaDetectionService,
mediaOptimizationService,
settingsService
);
ReflectionTestUtils.setField(service, "uploadLimitBytes", 5_000_000L);
}
@@ -78,7 +84,10 @@ class ChannelDirectoryServiceTest {
Optional<AssetView> created = service.createAsset("caster", file);
assertThat(created).isPresent();
ArgumentCaptor<Object> captor = ArgumentCaptor.forClass(Object.class);
verify(messagingTemplate).convertAndSend(org.mockito.ArgumentMatchers.contains("/topic/channel/caster"), captor.capture());
verify(messagingTemplate).convertAndSend(
org.mockito.ArgumentMatchers.contains("/topic/channel/caster"),
captor.capture()
);
}
@Test
@@ -105,8 +114,8 @@ class ChannelDirectoryServiceTest {
transform.setWidth(0);
assertThatThrownBy(() -> service.updateTransform(channel, id, transform))
.isInstanceOf(ResponseStatusException.class)
.hasMessageContaining("Canvas width out of range");
.isInstanceOf(ResponseStatusException.class)
.hasMessageContaining("Canvas width out of range");
}
@Test
@@ -118,15 +127,15 @@ class ChannelDirectoryServiceTest {
speedTransform.setSpeed(5.0);
assertThatThrownBy(() -> service.updateTransform(channel, id, speedTransform))
.isInstanceOf(ResponseStatusException.class)
.hasMessageContaining("Speed out of range");
.isInstanceOf(ResponseStatusException.class)
.hasMessageContaining("Speed out of range");
TransformRequest volumeTransform = validTransform();
volumeTransform.setAudioVolume(6.5);
assertThatThrownBy(() -> service.updateTransform(channel, id, volumeTransform))
.isInstanceOf(ResponseStatusException.class)
.hasMessageContaining("Audio volume out of range");
.isInstanceOf(ResponseStatusException.class)
.hasMessageContaining("Audio volume out of range");
}
@Test
@@ -178,44 +187,56 @@ class ChannelDirectoryServiceTest {
Map<String, Channel> channels = new ConcurrentHashMap<>();
Map<String, Asset> assets = new ConcurrentHashMap<>();
when(channelRepository.findById(anyString()))
.thenAnswer(invocation -> Optional.ofNullable(channels.get(invocation.getArgument(0))));
when(channelRepository.save(any(Channel.class)))
.thenAnswer(invocation -> {
Channel channel = invocation.getArgument(0);
channels.put(channel.getBroadcaster(), channel);
return channel;
});
when(channelRepository.findAll())
.thenAnswer(invocation -> List.copyOf(channels.values()));
when(channelRepository.findTop50ByBroadcasterContainingIgnoreCaseOrderByBroadcasterAsc(anyString()))
.thenAnswer(invocation -> channels.values().stream()
.filter(channel -> Optional.ofNullable(channel.getBroadcaster()).orElse("")
.contains(Optional.ofNullable(invocation.getArgument(0, String.class)).orElse("").toLowerCase()))
.sorted(Comparator.comparing(Channel::getBroadcaster))
.limit(50)
.toList());
when(channelRepository.findById(anyString())).thenAnswer((invocation) ->
Optional.ofNullable(channels.get(invocation.getArgument(0)))
);
when(channelRepository.save(any(Channel.class))).thenAnswer((invocation) -> {
Channel channel = invocation.getArgument(0);
channels.put(channel.getBroadcaster(), channel);
return channel;
});
when(channelRepository.findAll()).thenAnswer((invocation) -> List.copyOf(channels.values()));
when(channelRepository.findTop50ByBroadcasterContainingIgnoreCaseOrderByBroadcasterAsc(anyString())).thenAnswer(
(invocation) ->
channels
.values()
.stream()
.filter((channel) ->
Optional.ofNullable(channel.getBroadcaster())
.orElse("")
.contains(
Optional.ofNullable(invocation.getArgument(0, String.class)).orElse("").toLowerCase()
)
)
.sorted(Comparator.comparing(Channel::getBroadcaster))
.limit(50)
.toList()
);
when(assetRepository.save(any(Asset.class)))
.thenAnswer(invocation -> {
Asset asset = invocation.getArgument(0);
assets.put(asset.getId(), asset);
return asset;
});
when(assetRepository.findById(anyString()))
.thenAnswer(invocation -> Optional.ofNullable(assets.get(invocation.getArgument(0))));
when(assetRepository.findByBroadcaster(anyString()))
.thenAnswer(invocation -> filterAssetsByBroadcaster(assets.values(), invocation.getArgument(0), false));
when(assetRepository.findByBroadcasterAndHiddenFalse(anyString()))
.thenAnswer(invocation -> filterAssetsByBroadcaster(assets.values(), invocation.getArgument(0), true));
doAnswer(invocation -> assets.remove(invocation.getArgument(0, Asset.class).getId()))
.when(assetRepository).delete(any(Asset.class));
when(assetRepository.save(any(Asset.class))).thenAnswer((invocation) -> {
Asset asset = invocation.getArgument(0);
assets.put(asset.getId(), asset);
return asset;
});
when(assetRepository.findById(anyString())).thenAnswer((invocation) ->
Optional.ofNullable(assets.get(invocation.getArgument(0)))
);
when(assetRepository.findByBroadcaster(anyString())).thenAnswer((invocation) ->
filterAssetsByBroadcaster(assets.values(), invocation.getArgument(0), false)
);
when(assetRepository.findByBroadcasterAndHiddenFalse(anyString())).thenAnswer((invocation) ->
filterAssetsByBroadcaster(assets.values(), invocation.getArgument(0), true)
);
doAnswer((invocation) -> assets.remove(invocation.getArgument(0, Asset.class).getId()))
.when(assetRepository)
.delete(any(Asset.class));
}
private List<Asset> filterAssetsByBroadcaster(Collection<Asset> assets, String broadcaster, boolean onlyVisible) {
return assets.stream()
.filter(asset -> asset.getBroadcaster().equalsIgnoreCase(broadcaster))
.filter(asset -> !onlyVisible || !asset.isHidden())
.toList();
return assets
.stream()
.filter((asset) -> asset.getBroadcaster().equalsIgnoreCase(broadcaster))
.filter((asset) -> !onlyVisible || !asset.isHidden())
.toList();
}
}

View File

@@ -1,5 +1,7 @@
package dev.kruhlmann.imgfloat.config;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;
import org.springframework.http.RequestEntity;
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
@@ -12,39 +14,43 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResp
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.util.MultiValueMap;
import static org.assertj.core.api.Assertions.assertThat;
class TwitchAuthorizationCodeGrantRequestEntityConverterTest {
@Test
void addsClientIdAndSecretToTokenRequestBody() {
ClientRegistration registration = ClientRegistration.withRegistrationId("twitch")
.clientId("twitch-id")
.clientSecret("twitch-secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.redirectUri("https://example.com/redirect")
.scope("user:read:email")
.authorizationUri("https://id.twitch.tv/oauth2/authorize")
.tokenUri("https://id.twitch.tv/oauth2/token")
.userInfoUri("https://api.twitch.tv/helix/users")
.userNameAttributeName("preferred_username")
.build();
.clientId("twitch-id")
.clientSecret("twitch-secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.redirectUri("https://example.com/redirect")
.scope("user:read:email")
.authorizationUri("https://id.twitch.tv/oauth2/authorize")
.tokenUri("https://id.twitch.tv/oauth2/token")
.userInfoUri("https://api.twitch.tv/helix/users")
.userNameAttributeName("preferred_username")
.build();
OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest.authorizationCode()
.authorizationUri(registration.getProviderDetails().getAuthorizationUri())
.clientId(registration.getClientId())
.redirectUri(registration.getRedirectUri())
.state("state")
.build();
.authorizationUri(registration.getProviderDetails().getAuthorizationUri())
.clientId(registration.getClientId())
.redirectUri(registration.getRedirectUri())
.state("state")
.build();
OAuth2AuthorizationResponse authorizationResponse = OAuth2AuthorizationResponse.success("code")
.redirectUri(registration.getRedirectUri())
.state("state")
.build();
.redirectUri(registration.getRedirectUri())
.state("state")
.build();
OAuth2AuthorizationExchange exchange = new OAuth2AuthorizationExchange(authorizationRequest, authorizationResponse);
OAuth2AuthorizationCodeGrantRequest grantRequest = new OAuth2AuthorizationCodeGrantRequest(registration, exchange);
OAuth2AuthorizationExchange exchange = new OAuth2AuthorizationExchange(
authorizationRequest,
authorizationResponse
);
OAuth2AuthorizationCodeGrantRequest grantRequest = new OAuth2AuthorizationCodeGrantRequest(
registration,
exchange
);
var converter = new TwitchAuthorizationCodeGrantRequestEntityConverter();
RequestEntity<?> requestEntity = converter.convert(grantRequest);

View File

@@ -1,7 +1,11 @@
package dev.kruhlmann.imgfloat.config;
import java.net.URI;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
import java.net.URI;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
@@ -13,11 +17,6 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenRespon
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.web.client.RestTemplate;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
class TwitchOAuth2ErrorResponseErrorHandlerTest {
private final TwitchOAuth2ErrorResponseErrorHandler handler = new TwitchOAuth2ErrorResponseErrorHandler();
@@ -27,12 +26,12 @@ class TwitchOAuth2ErrorResponseErrorHandlerTest {
MockClientHttpResponse response = new MockClientHttpResponse(new byte[0], HttpStatus.BAD_REQUEST);
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
OAuth2AuthorizationException exception = assertThrows(OAuth2AuthorizationException.class,
() -> handler.handleError(response));
OAuth2AuthorizationException exception = assertThrows(OAuth2AuthorizationException.class, () ->
handler.handleError(response)
);
assertThat(exception.getError().getErrorCode()).isEqualTo("invalid_token_response");
assertThat(exception.getError().getDescription())
.contains("Failed to parse Twitch OAuth error response");
assertThat(exception.getError().getDescription()).contains("Failed to parse Twitch OAuth error response");
}
@Test
@@ -41,13 +40,20 @@ class TwitchOAuth2ErrorResponseErrorHandlerTest {
restTemplate.setErrorHandler(new TwitchOAuth2ErrorResponseErrorHandler());
MockRestServiceServer server = MockRestServiceServer.bindTo(restTemplate).build();
server.expect(requestTo("https://id.twitch.tv/oauth2/token"))
.andRespond(withSuccess(
"{\"access_token\":\"abc\",\"token_type\":\"bearer\",\"expires_in\":3600,\"scope\":[]}",
MediaType.APPLICATION_JSON));
server
.expect(requestTo("https://id.twitch.tv/oauth2/token"))
.andRespond(
withSuccess(
"{\"access_token\":\"abc\",\"token_type\":\"bearer\",\"expires_in\":3600,\"scope\":[]}",
MediaType.APPLICATION_JSON
)
);
RequestEntity<Void> request = RequestEntity.post(URI.create("https://id.twitch.tv/oauth2/token")).build();
ResponseEntity<OAuth2AccessTokenResponse> response = restTemplate.exchange(request, OAuth2AccessTokenResponse.class);
ResponseEntity<OAuth2AccessTokenResponse> response = restTemplate.exchange(
request,
OAuth2AccessTokenResponse.class
);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody()).isNotNull();

View File

@@ -8,7 +8,6 @@ import static org.springframework.test.web.client.response.MockRestResponseCreat
import java.time.Instant;
import java.util.Set;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
@@ -26,51 +25,54 @@ class TwitchOAuth2UserServiceTest {
@Test
void unwrapsTwitchUserAndAddsClientIdHeaderToUserInfoRequest() {
ClientRegistration registration = twitchRegistrationBuilder()
.clientId("client-123")
.clientSecret("secret")
.build();
.clientId("client-123")
.clientSecret("secret")
.build();
OAuth2UserRequest userRequest = userRequest(registration);
RestTemplate restTemplate = TwitchOAuth2UserService.createRestTemplate(userRequest);
MockRestServiceServer server = MockRestServiceServer.bindTo(restTemplate).build();
TwitchOAuth2UserService service = new TwitchOAuth2UserService(ignored -> restTemplate);
TwitchOAuth2UserService service = new TwitchOAuth2UserService((ignored) -> restTemplate);
server.expect(requestTo("https://api.twitch.tv/helix/users"))
.andExpect(method(HttpMethod.GET))
.andExpect(header("Client-ID", "client-123"))
.andRespond(withSuccess(
"{\"data\":[{\"id\":\"42\",\"login\":\"demo\",\"display_name\":\"Demo\"}]}",
MediaType.APPLICATION_JSON));
server
.expect(requestTo("https://api.twitch.tv/helix/users"))
.andExpect(method(HttpMethod.GET))
.andExpect(header("Client-ID", "client-123"))
.andRespond(
withSuccess(
"{\"data\":[{\"id\":\"42\",\"login\":\"demo\",\"display_name\":\"Demo\"}]}",
MediaType.APPLICATION_JSON
)
);
OAuth2User user = service.loadUser(userRequest);
assertThat(user.getName()).isEqualTo("demo");
assertThat(user.getAttributes())
.containsEntry("id", "42")
.containsEntry("display_name", "Demo");
assertThat(user.getAttributes()).containsEntry("id", "42").containsEntry("display_name", "Demo");
server.verify();
}
private OAuth2UserRequest userRequest(ClientRegistration registration) {
OAuth2AccessToken accessToken = new OAuth2AccessToken(
OAuth2AccessToken.TokenType.BEARER,
"token",
Instant.now(),
Instant.now().plusSeconds(60),
Set.of("user:read:email"));
OAuth2AccessToken.TokenType.BEARER,
"token",
Instant.now(),
Instant.now().plusSeconds(60),
Set.of("user:read:email")
);
return new OAuth2UserRequest(registration, accessToken);
}
private ClientRegistration.Builder twitchRegistrationBuilder() {
return ClientRegistration.withRegistrationId("twitch")
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
.clientName("Twitch")
.redirectUri("https://example.com/login/oauth2/code/twitch")
.authorizationUri("https://id.twitch.tv/oauth2/authorize")
.tokenUri("https://id.twitch.tv/oauth2/token")
.userInfoUri("https://api.twitch.tv/helix/users")
.userNameAttributeName("login");
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
.clientName("Twitch")
.redirectUri("https://example.com/login/oauth2/code/twitch")
.authorizationUri("https://id.twitch.tv/oauth2/authorize")
.tokenUri("https://id.twitch.tv/oauth2/token")
.userInfoUri("https://api.twitch.tv/helix/users")
.userNameAttributeName("login");
}
}

View File

@@ -1,18 +1,18 @@
package dev.kruhlmann.imgfloat.service;
import dev.kruhlmann.imgfloat.service.media.AssetContent;
import dev.kruhlmann.imgfloat.model.Asset;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import dev.kruhlmann.imgfloat.model.Asset;
import dev.kruhlmann.imgfloat.service.media.AssetContent;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class AssetStorageServiceTest {
private AssetStorageService service;
private Path assets;
private Path previews;
@@ -27,13 +27,13 @@ class AssetStorageServiceTest {
@Test
void refusesToStoreEmptyAsset() {
assertThatThrownBy(() -> service.storeAsset("caster", "id", new byte[0], "image/png"))
.isInstanceOf(IOException.class)
.hasMessageContaining("empty");
.isInstanceOf(IOException.class)
.hasMessageContaining("empty");
}
@Test
void storesAndLoadsAssets() throws IOException {
byte[] bytes = new byte[]{1, 2, 3};
byte[] bytes = new byte[] { 1, 2, 3 };
Asset asset = new Asset("caster", "asset", "http://example.com", 10, 10);
asset.setMediaType("image/png");
@@ -53,7 +53,7 @@ class AssetStorageServiceTest {
@Test
void storesAndLoadsPreviews() throws IOException {
byte[] preview = new byte[]{9, 8, 7};
byte[] preview = new byte[] { 9, 8, 7 };
Asset asset = new Asset("caster", "asset", "http://example.com", 10, 10);
asset.setMediaType("image/png");

View File

@@ -1,18 +1,18 @@
package dev.kruhlmann.imgfloat.service.media;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockMultipartFile;
import java.io.IOException;
import static org.assertj.core.api.Assertions.assertThat;
import java.io.IOException;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockMultipartFile;
class MediaDetectionServiceTest {
private final MediaDetectionService service = new MediaDetectionService();
@Test
void acceptsMagicBytesOverDeclaredType() throws IOException {
byte[] png = new byte[]{(byte) 0x89, 0x50, 0x4E, 0x47};
byte[] png = new byte[] { (byte) 0x89, 0x50, 0x4E, 0x47 };
MockMultipartFile file = new MockMultipartFile("file", "image.png", "text/plain", png);
assertThat(service.detectAllowedMediaType(file, file.getBytes())).contains("image/png");
@@ -20,14 +20,14 @@ class MediaDetectionServiceTest {
@Test
void fallsBackToFilenameAllowlist() throws IOException {
MockMultipartFile file = new MockMultipartFile("file", "picture.png", null, new byte[]{1, 2, 3});
MockMultipartFile file = new MockMultipartFile("file", "picture.png", null, new byte[] { 1, 2, 3 });
assertThat(service.detectAllowedMediaType(file, file.getBytes())).contains("image/png");
}
@Test
void rejectsUnknownTypes() throws IOException {
MockMultipartFile file = new MockMultipartFile("file", "unknown.bin", null, new byte[]{1, 2, 3});
MockMultipartFile file = new MockMultipartFile("file", "unknown.bin", null, new byte[] { 1, 2, 3 });
assertThat(service.detectAllowedMediaType(file, file.getBytes())).isEmpty();
}

View File

@@ -1,16 +1,16 @@
package dev.kruhlmann.imgfloat.service.media;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import static org.assertj.core.api.Assertions.assertThat;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import javax.imageio.ImageIO;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class MediaOptimizationServiceTest {
private MediaOptimizationService service;
@BeforeEach
@@ -38,7 +38,7 @@ class MediaOptimizationServiceTest {
@Test
void returnsNullForUnsupportedBytes() throws IOException {
OptimizedAsset optimized = service.optimizeAsset(new byte[]{1, 2, 3}, "application/octet-stream");
OptimizedAsset optimized = service.optimizeAsset(new byte[] { 1, 2, 3 }, "application/octet-stream");
assertThat(optimized).isNull();
}