Skip to content

Commit

Permalink
Delete finalizeUpload and add JSDoc
Browse files Browse the repository at this point in the history
  • Loading branch information
tohhsinpei committed Feb 25, 2022
1 parent ff4112f commit 9a71421
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 41 deletions.
115 changes: 107 additions & 8 deletions scripts/storage-emulator-integration/tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1342,7 +1342,7 @@ describe("Storage emulator", () => {
.expect(200);
});

it("#uploadResumableHandlesAuthError", async () => {
it("should handle auth error in resumable upload", async () => {
const uploadURL = await supertest(STORAGE_EMULATOR_HOST)
.post(
`/v0/b/${storageBucket}/o/test_upload.jpg?uploadType=resumable&name=test_upload.jpg`
Expand All @@ -1362,14 +1362,113 @@ describe("Storage emulator", () => {
"X-Goog-Upload-Command": "upload, finalize",
})
.expect(403);
});

await supertest(STORAGE_EMULATOR_HOST)
.put(uploadURL.pathname + uploadURL.search)
.set({
"X-Goog-Upload-Protocol": "resumable",
"X-Goog-Upload-Command": "cancel",
})
.expect(200);
describe("cancelUpload", () => {
it("should cancel upload successfully", async () => {
const uploadURL = await supertest(STORAGE_EMULATOR_HOST)
.post(
`/v0/b/${storageBucket}/o/test_upload.jpg?uploadType=resumable&name=test_upload.jpg`
)
.set({
Authorization: "Bearer owner",
"X-Goog-Upload-Protocol": "resumable",
"X-Goog-Upload-Command": "start",
})
.expect(200)
.then((res) => new URL(res.header["x-goog-upload-url"]));

await supertest(STORAGE_EMULATOR_HOST)
.put(uploadURL.pathname + uploadURL.search)
.set({
"X-Goog-Upload-Protocol": "resumable",
"X-Goog-Upload-Command": "cancel",
})
.expect(200);
});

it("should handle cancelling already cancelled upload", async () => {
const uploadURL = await supertest(STORAGE_EMULATOR_HOST)
.post(
`/v0/b/${storageBucket}/o/test_upload.jpg?uploadType=resumable&name=test_upload.jpg`
)
.set({
Authorization: "Bearer owner",
"X-Goog-Upload-Protocol": "resumable",
"X-Goog-Upload-Command": "start",
})
.expect(200)
.then((res) => new URL(res.header["x-goog-upload-url"]));

await supertest(STORAGE_EMULATOR_HOST)
.put(uploadURL.pathname + uploadURL.search)
.set({
"X-Goog-Upload-Protocol": "resumable",
"X-Goog-Upload-Command": "cancel",
})
.expect(200);

await supertest(STORAGE_EMULATOR_HOST)
.put(uploadURL.pathname + uploadURL.search)
.set({
"X-Goog-Upload-Protocol": "resumable",
"X-Goog-Upload-Command": "cancel",
})
.expect(200);
});

it("should handle cancelling finalized resumable upload", async () => {
const uploadURL = await supertest(STORAGE_EMULATOR_HOST)
.post(
`/v0/b/${storageBucket}/o/test_upload.jpg?uploadType=resumable&name=test_upload.jpg`
)
.set({
Authorization: "Bearer owner",
"X-Goog-Upload-Protocol": "resumable",
"X-Goog-Upload-Command": "start",
})
.expect(200)
.then((res) => new URL(res.header["x-goog-upload-url"]));

await supertest(STORAGE_EMULATOR_HOST)
.put(uploadURL.pathname + uploadURL.search)
.set({
"X-Goog-Upload-Protocol": "resumable",
"X-Goog-Upload-Command": "upload, finalize",
})
.expect(200);

await supertest(STORAGE_EMULATOR_HOST)
.put(uploadURL.pathname + uploadURL.search)
.set({
"X-Goog-Upload-Protocol": "resumable",
"X-Goog-Upload-Command": "cancel",
})
.expect(400);
});

it("should handle cancelling non-existent upload", async () => {
const uploadURL = await supertest(STORAGE_EMULATOR_HOST)
.post(
`/v0/b/${storageBucket}/o/test_upload.jpg?uploadType=resumable&name=test_upload.jpg`
)
.set({
Authorization: "Bearer owner",
"X-Goog-Upload-Protocol": "resumable",
"X-Goog-Upload-Command": "start",
})
.expect(200)
.then((res) => new URL(res.header["x-goog-upload-url"]));

console.log(uploadURL);
await supertest(STORAGE_EMULATOR_HOST)
.put(uploadURL.pathname + uploadURL.search.replace(/(upload_id=).*?(&)/, '$1foo$2'))
.set({
"X-Goog-Upload-Protocol": "resumable",
"X-Goog-Upload-Command": "cancel",
})
.expect(404);
});
});
});

Expand Down
19 changes: 11 additions & 8 deletions src/emulator/storage/apis/firebase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { StorageEmulator } from "../index";
import { EmulatorRegistry } from "../../registry";
import { StorageRulesetInstance } from "../rules/runtime";
import { RulesetOperationMethod } from "../rules/types";
import { UploadStatus } from "../files";

async function isPermitted(opts: {
ruleset?: StorageRulesetInstance;
Expand Down Expand Up @@ -495,10 +496,12 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router {
if (uploadCommand == "cancel") {
const upload = storageLayer.cancelUpload(uploadId);
if (!upload) {
res.sendStatus(404);
} else if (upload.status === UploadStatus.FINISHED) {
res.sendStatus(400);
return;
} else {
res.sendStatus(200);
}
res.sendStatus(200);
return;
}

Expand Down Expand Up @@ -530,12 +533,12 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router {
}

if (uploadCommand.includes("finalize")) {
const finalizedUpload = storageLayer.finishUpload(uploadId);
if (!finalizedUpload) {
const finishedUpload = storageLayer.storeFinishedUpload(uploadId);
if (!finishedUpload) {
res.sendStatus(400);
return;
}
upload = finalizedUpload.upload;
upload = finishedUpload.upload;

// For resumable uploads, we check auth on finalization in case of byte-dependant rules
if (
Expand All @@ -560,14 +563,14 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router {
}

res.header("x-goog-upload-status", "final");
storageLayer.persistUpload(finalizedUpload);
storageLayer.persistsUpload(finishedUpload);

const md = finalizedUpload.file.metadata;
const md = finishedUpload.file.metadata;
if (md.downloadTokens.length == 0) {
md.addDownloadToken();
}

res.json(new OutgoingFirebaseMetadata(finalizedUpload.file.metadata));
res.json(new OutgoingFirebaseMetadata(finishedUpload.file.metadata));
} else if (!upload) {
res.sendStatus(400);
return;
Expand Down
10 changes: 6 additions & 4 deletions src/emulator/storage/apis/gcloud.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,13 +130,15 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router {
return;
}

const finalizedUpload = storageLayer.finalizeUpload(uploadId);
if (!finalizedUpload) {
const finishedUpload = storageLayer.storeFinishedUpload(uploadId);
if (!finishedUpload) {
res.sendStatus(400);
return;
}
upload = finalizedUpload.upload;
res.status(200).json(new CloudStorageObjectMetadata(finalizedUpload.file.metadata)).send();
storageLayer.persistsUpload(finishedUpload);

upload = finishedUpload.upload;
res.status(200).json(new CloudStorageObjectMetadata(finishedUpload.file.metadata)).send();
});

gcloudStorageAPI.post("/b/:bucketId/o/:objectId/acl", (req, res) => {
Expand Down
37 changes: 18 additions & 19 deletions src/emulator/storage/files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ export enum UploadStatus {
FINISHED,
}

export type FinalizedUpload = {
export type FinishedUpload = {
upload: ResumableUpload;
file: StoredFile;
};
Expand Down Expand Up @@ -222,10 +222,10 @@ export class StorageLayer {
return undefined;
}

if (upload.status !== UploadStatus.FINISHED) {
if (upload.status === UploadStatus.ACTIVE) {
this._persistence.deleteFile(upload.fileLocation);
upload.status = UploadStatus.CANCELLED;
}
upload.status = UploadStatus.CANCELLED;
return upload;
}

Expand Down Expand Up @@ -270,7 +270,12 @@ export class StorageLayer {
return this._persistence.deleteAll();
}

public finishUpload(uploadId: string): FinalizedUpload | undefined {
/**
* Stores the file with generated metadata as part of finalizing an upload.
* @param uploadId The id of the upload to finalize.
* @returns The uploaded file if uploadId is valid and undefined otherwise.
*/
public storeFinishedUpload(uploadId: string): FinishedUpload | undefined {
const upload = this._uploads.get(uploadId);

if (!upload) {
Expand All @@ -295,30 +300,24 @@ export class StorageLayer {
const file = new StoredFile(finalMetadata, filePath);
this._files.set(filePath, file);

return {upload, file};
return { upload, file };
}

public persistUpload(finalizedUpload: FinalizedUpload): void {
const {upload, file} = finalizedUpload;
/**
* Updates the uploaded file's location in persistence layer and triggers CREATE Cloud Functions.
* To be called as the last step of finalizing an upload.
* @param finishedUpload The upload to finalize and its corresponding file.
*/
public persistsUpload(finishedUpload: FinishedUpload): void {
const { upload, file } = finishedUpload;
const filePath = this.path(upload.bucketId, upload.objectId);

this._persistence.deleteFile(filePath, true);
this._persistence.deleteFile(filePath, /* failSilently = */ true);
this._persistence.renameFile(upload.fileLocation, filePath);

this._cloudFunctions.dispatch("finalize", new CloudStorageObjectMetadata(file.metadata));
}

public finalizeUpload(uploadId: string): FinalizedUpload | undefined {
const finalizedUpload = this.finishUpload(uploadId);

if (!finalizedUpload) {
return undefined;
}

this.persistUpload(finalizedUpload);
return finalizedUpload;
}

public oneShotUpload(
bucket: string,
object: string,
Expand Down
4 changes: 2 additions & 2 deletions src/test/emulators/storage/files.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ describe("files", () => {
expect(deserialized).to.deep.equal(metadata);
});

it("finishUpload persists file to memory", () => {
it("stores uploaded file in memory", () => {
const storageLayer = new StorageLayer("project");
const { uploadId } = storageLayer.startUpload("bucket", "object", "mime/type", {
contentType: "mime/type",
});
storageLayer.uploadBytes(uploadId, Buffer.alloc(0));
storageLayer.finishUpload(uploadId);
storageLayer.storeFinishedUpload(uploadId);
expect(storageLayer.getMetadata("bucket", "object")).not.to.equal(undefined);
});
});

0 comments on commit 9a71421

Please sign in to comment.