Skip to content

Commit 7b3f9a2

Browse files
authored
Merge pull request #1836 from NASA-AMMOS/feat/simultaneous-workspace-file-edit-protections
Add simultaneous-edit protection to workspace file saves
2 parents cfcb123 + 4dcf014 commit 7b3f9a2

11 files changed

Lines changed: 8511 additions & 57 deletions

File tree

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package gov.nasa.jpl.aerie.e2e;
2+
3+
import com.microsoft.playwright.Playwright;
4+
import gov.nasa.jpl.aerie.e2e.utils.GatewayRequests;
5+
import gov.nasa.jpl.aerie.e2e.utils.HasuraRequests;
6+
import gov.nasa.jpl.aerie.e2e.utils.WorkspaceRequests;
7+
import org.junit.jupiter.api.AfterAll;
8+
import org.junit.jupiter.api.AfterEach;
9+
import org.junit.jupiter.api.BeforeAll;
10+
import org.junit.jupiter.api.BeforeEach;
11+
import org.junit.jupiter.api.Test;
12+
13+
import java.io.IOException;
14+
import java.nio.file.Path;
15+
16+
import static gov.nasa.jpl.aerie.e2e.types.User.admin;
17+
import static org.junit.jupiter.api.Assertions.assertNotEquals;
18+
19+
public class WorkspaceETagTests {
20+
// Requests
21+
private static Playwright playwright;
22+
private static HasuraRequests hasura;
23+
private static WorkspaceRequests wsServer;
24+
25+
// Class-Wide Data
26+
private static int cdictId;
27+
private static int parcelId;
28+
29+
private static String adminToken;
30+
31+
private int workspaceId;
32+
33+
@BeforeAll
34+
static void beforeAll() throws IOException {
35+
// Setup Requests
36+
playwright = Playwright.create();
37+
hasura = new HasuraRequests(playwright);
38+
wsServer = new WorkspaceRequests(playwright);
39+
40+
// Get valid JWT tokens for the users
41+
try (final var gateway = new GatewayRequests(playwright)) {
42+
adminToken = gateway.login(admin);
43+
}
44+
45+
// Set up parcel and dictionary to use across the tests
46+
cdictId = hasura.createMockCommandDictionary("WorkspaceETagTest", "Workspace E2E Test");
47+
parcelId = hasura.createMockParcel("Workspace ETag Parcel", cdictId);
48+
}
49+
50+
@AfterAll
51+
static void afterAll() throws IOException {
52+
// Cleanup parcel and dictionary
53+
hasura.deleteMockCommandDictionary(cdictId);
54+
hasura.deleteMockParcel(parcelId);
55+
56+
// Cleanup Requests
57+
wsServer.close();
58+
hasura.close();
59+
playwright.close();
60+
}
61+
62+
@BeforeEach
63+
void beforeEach() throws IOException {
64+
workspaceId = wsServer.createWorkspace("ETagWSTests", parcelId);
65+
}
66+
67+
@AfterEach
68+
void afterEach() throws IOException {
69+
wsServer.deleteWorkspace(workspaceId);
70+
}
71+
72+
/**
73+
* When generating Entity Tags for a file, the WS server reads the file in 1MB chunks.
74+
* This test uploads two 1.2MB files that are the exact same size and differ solely after the 1MB mark,
75+
* then compares the generated ETags.
76+
*/
77+
@Test
78+
void chunkingDoesNotAffectETags() throws IOException {
79+
final var versionAPath = Path.of("versionA.txt");
80+
final var versionBPath = Path.of("versionB.txt");
81+
82+
// Upload the two files
83+
wsServer.putFile(
84+
adminToken,
85+
workspaceId,
86+
versionAPath,
87+
Path.of("workspaces", "workspace_etag_test_original.txt"));
88+
wsServer.putFile(
89+
adminToken,
90+
workspaceId,
91+
versionBPath,
92+
Path.of("workspaces", "workspace_etag_test_modified.txt"));
93+
94+
// GET the files
95+
final var versionA = wsServer.get(adminToken, workspaceId, versionAPath);
96+
final var versionB = wsServer.get(adminToken, workspaceId, versionBPath);
97+
98+
// Compare the eTag headers
99+
final var versionAETag = versionA.headers().get("etag");
100+
final var versionBETag = versionB.headers().get("etag");
101+
assertNotEquals(versionAETag, versionBETag);
102+
}
103+
}

e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/utils/WorkspaceRequests.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import javax.json.JsonObject;
1515
import java.io.IOException;
1616
import java.nio.charset.StandardCharsets;
17+
import java.nio.file.Files;
1718
import java.nio.file.Path;
1819
import java.util.List;
1920
import java.util.Map;
@@ -143,6 +144,30 @@ public APIResponse putFile(String token, int workspaceId, Path fileLocation, Str
143144
return request.put(SINGLE_ITEM_URL.formatted(workspaceId, fileLocation), options);
144145
}
145146

147+
/**
148+
* Upload a file located in the test resources to the Workspace Server using the 'PUT file' endpoint.
149+
* Does not pass the "overwrite" flag.
150+
* @param token The JWT token for the user making the request
151+
* @param workspaceId The workspace to insert the file into
152+
* @param fileLocation Where to place the file in the workspace
153+
* @param resourcePath The Path to the file within the test resources folder (src/test/resources)
154+
* @return The APIResponse from the server
155+
*/
156+
public APIResponse putFile(String token, int workspaceId, Path fileLocation, Path resourcePath) throws IOException
157+
{
158+
byte[] buffer = Files.readAllBytes(Path.of("src/test/resources/", resourcePath.toString()));
159+
FilePayload filePayload = new FilePayload(
160+
fileLocation.getFileName().toString(),
161+
"text/plain",
162+
buffer);
163+
final var options = RequestOptions
164+
.create()
165+
.setQueryParam("type", "file")
166+
.setHeader("Authorization", "Bearer "+token)
167+
.setMultipart(FormData.create().set("file", filePayload));
168+
return request.put(SINGLE_ITEM_URL.formatted(workspaceId, fileLocation), options);
169+
}
170+
146171
/**
147172
* Call the 'File PUT' endpoint in the Workspace server. Passes the overwrite flag
148173
* @param token The JWT token for the user making the request

0 commit comments

Comments
 (0)