添加快传7天离线传
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
package com.yoyuzh.config;
|
||||
|
||||
import com.yoyuzh.PortalBackendApplication;
|
||||
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.redirectedUrl;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
@SpringBootTest(
|
||||
classes = PortalBackendApplication.class,
|
||||
properties = {
|
||||
"spring.datasource.url=jdbc:h2:mem:api_root_test;MODE=MySQL;DB_CLOSE_DELAY=-1",
|
||||
"spring.datasource.driver-class-name=org.h2.Driver",
|
||||
"spring.datasource.username=sa",
|
||||
"spring.datasource.password=",
|
||||
"spring.jpa.hibernate.ddl-auto=create-drop",
|
||||
"app.jwt.secret=0123456789abcdef0123456789abcdef"
|
||||
}
|
||||
)
|
||||
@AutoConfigureMockMvc
|
||||
class ApiRootControllerIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Test
|
||||
void shouldRedirectRootPathToSwaggerUi() throws Exception {
|
||||
mockMvc.perform(get("/"))
|
||||
.andExpect(status().isFound())
|
||||
.andExpect(redirectedUrl("/swagger-ui.html"));
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,14 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class SecurityConfigTest {
|
||||
|
||||
@Test
|
||||
void corsPropertiesShouldAllowProductionSiteOriginsByDefault() {
|
||||
CorsProperties corsProperties = new CorsProperties();
|
||||
|
||||
assertThat(corsProperties.getAllowedOrigins())
|
||||
.contains("https://yoyuzh.xyz", "https://www.yoyuzh.xyz");
|
||||
}
|
||||
|
||||
@Test
|
||||
void corsConfigurationShouldAllowPatchRequests() {
|
||||
CorsProperties corsProperties = new CorsProperties();
|
||||
|
||||
@@ -9,14 +9,20 @@ 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.security.test.context.support.WithMockUser;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
@SpringBootTest(
|
||||
@@ -60,8 +66,9 @@ class TransferControllerIntegrationTest {
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("""
|
||||
{
|
||||
"mode": "ONLINE",
|
||||
"files": [
|
||||
{"name": "report.pdf", "size": 2048, "contentType": "application/pdf"}
|
||||
{"name": "report.pdf", "relativePath": "课程资料/report.pdf", "size": 2048, "contentType": "application/pdf"}
|
||||
]
|
||||
}
|
||||
"""))
|
||||
@@ -69,7 +76,9 @@ class TransferControllerIntegrationTest {
|
||||
.andExpect(jsonPath("$.code").value(0))
|
||||
.andExpect(jsonPath("$.data.sessionId").isNotEmpty())
|
||||
.andExpect(jsonPath("$.data.pickupCode").isString())
|
||||
.andExpect(jsonPath("$.data.mode").value("ONLINE"))
|
||||
.andExpect(jsonPath("$.data.files[0].name").value("report.pdf"))
|
||||
.andExpect(jsonPath("$.data.files[0].relativePath").value("课程资料/report.pdf"))
|
||||
.andReturn()
|
||||
.getResponse()
|
||||
.getContentAsString();
|
||||
@@ -80,11 +89,13 @@ class TransferControllerIntegrationTest {
|
||||
mockMvc.perform(get("/api/transfer/sessions/lookup").param("pickupCode", pickupCode))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.sessionId").value(sessionId))
|
||||
.andExpect(jsonPath("$.data.pickupCode").value(pickupCode));
|
||||
.andExpect(jsonPath("$.data.pickupCode").value(pickupCode))
|
||||
.andExpect(jsonPath("$.data.mode").value("ONLINE"));
|
||||
|
||||
mockMvc.perform(post("/api/transfer/sessions/{sessionId}/join", sessionId))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.sessionId").value(sessionId))
|
||||
.andExpect(jsonPath("$.data.mode").value("ONLINE"))
|
||||
.andExpect(jsonPath("$.data.files[0].name").value("report.pdf"));
|
||||
|
||||
mockMvc.perform(post("/api/transfer/sessions/{sessionId}/signals", sessionId)
|
||||
@@ -113,11 +124,71 @@ class TransferControllerIntegrationTest {
|
||||
mockMvc.perform(post("/api/transfer/sessions")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("""
|
||||
{"files":[{"name":"demo.txt","size":12,"contentType":"text/plain"}]}
|
||||
{"mode":"ONLINE","files":[{"name":"demo.txt","relativePath":"demo.txt","size":12,"contentType":"text/plain"}]}
|
||||
"""))
|
||||
.andExpect(status().isUnauthorized());
|
||||
|
||||
mockMvc.perform(post("/api/transfer/sessions/{sessionId}/join", "missing-session"))
|
||||
.andExpect(status().isNotFound());
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(username = "alice")
|
||||
void shouldPersistOfflineTransfersForSevenDaysAndAllowRepeatedDownloads() throws Exception {
|
||||
String response = mockMvc.perform(post("/api/transfer/sessions")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("""
|
||||
{
|
||||
"mode": "OFFLINE",
|
||||
"files": [
|
||||
{"name": "offline.txt", "relativePath": "资料/offline.txt", "size": 13, "contentType": "text/plain"}
|
||||
]
|
||||
}
|
||||
"""))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.mode").value("OFFLINE"))
|
||||
.andExpect(jsonPath("$.data.files[0].id").isString())
|
||||
.andExpect(jsonPath("$.data.files[0].relativePath").value("资料/offline.txt"))
|
||||
.andReturn()
|
||||
.getResponse()
|
||||
.getContentAsString();
|
||||
|
||||
String sessionId = com.jayway.jsonpath.JsonPath.read(response, "$.data.sessionId");
|
||||
String pickupCode = com.jayway.jsonpath.JsonPath.read(response, "$.data.pickupCode");
|
||||
String fileId = com.jayway.jsonpath.JsonPath.read(response, "$.data.files[0].id");
|
||||
String expiresAtRaw = com.jayway.jsonpath.JsonPath.read(response, "$.data.expiresAt");
|
||||
|
||||
Instant expiresAt = Instant.parse(expiresAtRaw);
|
||||
assertThat(expiresAt).isAfter(Instant.now().plusSeconds(6 * 24 * 60 * 60L));
|
||||
|
||||
MockMultipartFile offlineFile = new MockMultipartFile(
|
||||
"file",
|
||||
"offline.txt",
|
||||
MediaType.TEXT_PLAIN_VALUE,
|
||||
"hello offline".getBytes(StandardCharsets.UTF_8)
|
||||
);
|
||||
|
||||
mockMvc.perform(multipart("/api/transfer/sessions/{sessionId}/files/{fileId}/content", sessionId, fileId)
|
||||
.file(offlineFile))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(0));
|
||||
|
||||
mockMvc.perform(get("/api/transfer/sessions/lookup").param("pickupCode", pickupCode))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.sessionId").value(sessionId))
|
||||
.andExpect(jsonPath("$.data.mode").value("OFFLINE"));
|
||||
|
||||
mockMvc.perform(post("/api/transfer/sessions/{sessionId}/join", sessionId))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.mode").value("OFFLINE"))
|
||||
.andExpect(jsonPath("$.data.files[0].name").value("offline.txt"));
|
||||
|
||||
mockMvc.perform(get("/api/transfer/sessions/{sessionId}/files/{fileId}/download", sessionId, fileId))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().bytes("hello offline".getBytes(StandardCharsets.UTF_8)));
|
||||
|
||||
mockMvc.perform(get("/api/transfer/sessions/{sessionId}/files/{fileId}/download", sessionId, fileId))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().bytes("hello offline".getBytes(StandardCharsets.UTF_8)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,13 @@ import org.junit.jupiter.api.Test;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class TransferSessionTest {
|
||||
|
||||
@Test
|
||||
void shouldEmitPeerJoinedOnlyOnceWhenReceiverJoinsRepeatedly() {
|
||||
void shouldEmitPeerJoinedOnFirstReceiverJoin() {
|
||||
TransferSession session = new TransferSession(
|
||||
"session-1",
|
||||
"849201",
|
||||
@@ -18,7 +19,6 @@ class TransferSessionTest {
|
||||
List.of(new TransferFileItem("report.pdf", 2048, "application/pdf"))
|
||||
);
|
||||
|
||||
session.markReceiverJoined();
|
||||
session.markReceiverJoined();
|
||||
|
||||
PollTransferSignalsResponse senderSignals = session.poll(TransferRole.SENDER, 0);
|
||||
@@ -29,6 +29,21 @@ class TransferSessionTest {
|
||||
assertThat(senderSignals.nextCursor()).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectRepeatedReceiverJoinForOnlineTransfer() {
|
||||
TransferSession session = new TransferSession(
|
||||
"session-1",
|
||||
"849201",
|
||||
Instant.parse("2026-03-20T12:00:00Z"),
|
||||
List.of(new TransferFileItem("report.pdf", 2048, "application/pdf"))
|
||||
);
|
||||
|
||||
session.markReceiverJoined();
|
||||
|
||||
assertThatThrownBy(session::markReceiverJoined)
|
||||
.hasMessageContaining("在线快传");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRouteSignalsToTheOppositeRoleQueue() {
|
||||
TransferSession session = new TransferSession(
|
||||
|
||||
Reference in New Issue
Block a user