describe()

in scripts/storage-emulator-integration/tests.ts [158:573]


  describe("Admin SDK Endpoints", function (this) {
    // eslint-disable-next-line @typescript-eslint/no-invalid-this
    this.timeout(TEST_SETUP_TIMEOUT);
    let testBucket: Bucket;

    before(async () => {
      if (!TEST_CONFIG.useProductionServers) {
        process.env.STORAGE_EMULATOR_HOST = STORAGE_EMULATOR_HOST;

        test = new TriggerEndToEndTest(FIREBASE_PROJECT, __dirname, emulatorConfig);
        await test.startEmulators(["--only", "auth,storage"]);
      }

      // TODO: We should not need a real credential for emulator tests, but
      //       today we do.
      const credential = fs.existsSync(path.join(__dirname, SERVICE_ACCOUNT_KEY))
        ? admin.credential.cert(readJson(SERVICE_ACCOUNT_KEY))
        : admin.credential.applicationDefault();

      admin.initializeApp({
        credential,
      });

      testBucket = admin.storage().bucket(storageBucket);

      smallFilePath = createRandomFile("small_file", SMALL_FILE_SIZE);
      largeFilePath = createRandomFile("large_file", LARGE_FILE_SIZE);
    });

    beforeEach(async () => {
      if (!TEST_CONFIG.useProductionServers) {
        await resetStorageEmulator(STORAGE_EMULATOR_HOST);
      } else {
        await testBucket.deleteFiles();
      }
    });

    describe(".bucket()", () => {
      describe("#upload()", () => {
        it("should handle non-resumable uploads", async () => {
          await testBucket.upload(smallFilePath, {
            resumable: false,
          });
          // Doesn't require an assertion, will throw on failure
        });

        it("should replace existing file on upload", async () => {
          const path = "replace.txt";
          const content1 = createRandomFile("small_content_1", 10);
          const content2 = createRandomFile("small_content_2", 10);
          const file = testBucket.file(path);

          await testBucket.upload(content1, {
            destination: path,
          });

          const [readContent1] = await file.download();

          expect(readContent1).to.deep.equal(fs.readFileSync(content1));

          await testBucket.upload(content2, {
            destination: path,
          });

          const [readContent2] = await file.download();
          expect(readContent2).to.deep.equal(fs.readFileSync(content2));

          fs.unlinkSync(content1);
          fs.unlinkSync(content2);
        });

        it("should handle gzip'd uploads", async () => {
          // This appears to pass, but the file gets corrupted cause it's gzipped?
          // expect(true).to.be.false;
          await testBucket.upload(smallFilePath, {
            gzip: true,
          });
        });

        // TODO(abehaskins): This test is temporarily disabled due to a credentials issue
        it.skip("should handle large (resumable) uploads", async () => {
          await testBucket.upload(largeFilePath),
            {
              resumable: true,
            };
        });
      });

      describe("#getFiles()", () => {
        it("should list files", async () => {
          await testBucket.upload(smallFilePath, {
            destination: "testing/shoveler.svg",
          });
          const [files, prefixes] = await testBucket.getFiles({
            directory: "testing",
          });

          expect(prefixes).to.be.undefined;
          expect(files.map((file) => file.name)).to.deep.equal(["testing/shoveler.svg"]);
        });
      });
    });

    describe(".file()", () => {
      describe("#save()", () => {
        // TODO(abehaskins): This test is temporarily disabled due to a credentials issue
        it.skip("should accept a zero-byte file", async () => {
          await testBucket.file("testing/dir/").save("");

          const [files] = await testBucket.getFiles({
            directory: "testing",
          });

          expect(files.map((file) => file.name)).to.contain("testing/dir/");
        });
      });

      describe("#get()", () => {
        // TODO(abehaskins): This test is temporarily disabled due to a credentials issue
        it.skip("should complete an save/get/download cycle", async () => {
          const p = "testing/dir/hello.txt";
          const content = "hello, world";

          await testBucket.file(p).save(content);

          const [f] = await testBucket.file(p).get();
          const [buf] = await f.download();

          expect(buf.toString()).to.equal(content);
        });
      });

      describe("#delete()", () => {
        it("should properly delete a file from the bucket", async () => {
          // We use a nested path to ensure that we don't need to decode
          // the objectId in the gcloud emulator API
          const bucketFilePath = "file/to/delete";
          await testBucket.upload(smallFilePath, {
            destination: bucketFilePath,
          });

          // Get a reference to the uploaded file
          const toDeleteFile = testBucket.file(bucketFilePath);

          // Ensure that the file exists on the bucket before deleting it
          const [existsBefore] = await toDeleteFile.exists();
          expect(existsBefore).to.equal(true);

          // Delete it
          await toDeleteFile.delete();
          // Ensure that it doesn't exist anymore on the bucket
          const [existsAfter] = await toDeleteFile.exists();
          expect(existsAfter).to.equal(false);
        });
      });

      describe("#download()", () => {
        it("should return the content of the file", async () => {
          await testBucket.upload(smallFilePath);
          const [downloadContent] = await testBucket
            .file(smallFilePath.split("/").slice(-1)[0])
            .download();

          const actualContent = fs.readFileSync(smallFilePath);
          expect(downloadContent).to.deep.equal(actualContent);
        });
      });

      describe("#makePublic()", () => {
        it("should no-op", async () => {
          const destination = "a/b";
          await testBucket.upload(smallFilePath, { destination });
          const [aclMetadata] = await testBucket.file(destination).makePublic();

          const generation = aclMetadata.generation;
          delete aclMetadata.generation;

          expect(aclMetadata).to.deep.equal({
            kind: "storage#objectAccessControl",
            object: destination,
            id: `${testBucket.name}/${destination}/${generation}/allUsers`,
            selfLink: `${STORAGE_EMULATOR_HOST}/storage/v1/b/${
              testBucket.name
            }/o/${encodeURIComponent(destination)}/acl/allUsers`,
            bucket: testBucket.name,
            entity: "allUsers",
            role: "READER",
            etag: "someEtag",
          });
        });

        it("should not interfere with downloading of bytes via public URL", async () => {
          const destination = "a/b";
          await testBucket.upload(smallFilePath, { destination });
          await testBucket.file(destination).makePublic();

          const publicLink = `${STORAGE_EMULATOR_HOST}/${testBucket.name}/${destination}`;

          const requestClient = TEST_CONFIG.useProductionServers ? https : http;
          await new Promise((resolve, reject) => {
            requestClient.get(publicLink, {}, (response) => {
              const data: any = [];
              response
                .on("data", (chunk) => data.push(chunk))
                .on("end", () => {
                  expect(Buffer.concat(data).length).to.equal(SMALL_FILE_SIZE);
                })
                .on("close", resolve)
                .on("error", reject);
            });
          });
        });
      });

      describe("#getMetadata()", () => {
        it("should throw on non-existing file", async () => {
          let err: any;
          await testBucket
            .file(smallFilePath)
            .getMetadata()
            .catch((_err) => {
              err = _err;
            });

          expect(err).to.not.be.empty;
        });

        it("should return generated metadata for new upload", async () => {
          await testBucket.upload(smallFilePath);
          const [metadata] = await testBucket
            .file(smallFilePath.split("/").slice(-1)[0])
            .getMetadata();

          const metadataTypes: { [s: string]: string } = {};

          for (const key in metadata) {
            if (metadata[key]) {
              metadataTypes[key] = typeof metadata[key];
            }
          }

          expect(metadataTypes).to.deep.equal({
            bucket: "string",
            contentType: "string",
            generation: "string",
            md5Hash: "string",
            crc32c: "string",
            etag: "string",
            metageneration: "string",
            storageClass: "string",
            name: "string",
            size: "string",
            timeCreated: "string",
            updated: "string",
            id: "string",
            kind: "string",
            mediaLink: "string",
            selfLink: "string",
            timeStorageClassUpdated: "string",
          });
        });

        it("should return a functional media link", async () => {
          await testBucket.upload(smallFilePath);
          const [{ mediaLink }] = await testBucket
            .file(smallFilePath.split("/").slice(-1)[0])
            .getMetadata();

          const requestClient = TEST_CONFIG.useProductionServers ? https : http;
          await new Promise((resolve, reject) => {
            requestClient.get(mediaLink, {}, (response) => {
              const data: any = [];
              response
                .on("data", (chunk) => data.push(chunk))
                .on("end", () => {
                  expect(Buffer.concat(data).length).to.equal(SMALL_FILE_SIZE);
                })
                .on("close", resolve)
                .on("error", reject);
            });
          });
        });

        it("should handle firebaseStorageDownloadTokens", async () => {
          const destination = "public/small_file";
          await testBucket.upload(smallFilePath, {
            destination,
            metadata: {},
          });

          const cloudFile = testBucket.file(destination);
          const md = {
            metadata: {
              firebaseStorageDownloadTokens: "myFirstToken,mySecondToken",
            },
          };

          await cloudFile.setMetadata(md);

          // Check that the tokens are saved in Firebase metadata
          await supertest(STORAGE_EMULATOR_HOST)
            .get(`/v0/b/${testBucket.name}/o/${encodeURIComponent(destination)}`)
            .expect(200)
            .then((res) => {
              const firebaseMd = res.body;
              expect(firebaseMd.downloadTokens).to.equal(md.metadata.firebaseStorageDownloadTokens);
            });

          // Check that the tokens are saved in Cloud metadata
          const [metadata] = await cloudFile.getMetadata();
          expect(metadata.metadata.firebaseStorageDownloadTokens).to.deep.equal(
            md.metadata.firebaseStorageDownloadTokens
          );
        });
      });

      describe("#setMetadata()", () => {
        it("should throw on non-existing file", async () => {
          let err: any;
          await testBucket
            .file(smallFilePath)
            .setMetadata({ contentType: 9000 })
            .catch((_err) => {
              err = _err;
            });

          expect(err).to.not.be.empty;
        });

        it("should allow overriding of default metadata", async () => {
          await testBucket.upload(smallFilePath);
          const [metadata] = await testBucket
            .file(smallFilePath.split("/").slice(-1)[0])
            .setMetadata({ contentType: "very/fake" });

          const metadataTypes: { [s: string]: string } = {};

          for (const key in metadata) {
            if (metadata[key]) {
              metadataTypes[key] = typeof metadata[key];
            }
          }

          expect(metadata.contentType).to.equal("very/fake");
          expect(metadataTypes).to.deep.equal({
            bucket: "string",
            contentType: "string",
            generation: "string",
            md5Hash: "string",
            crc32c: "string",
            etag: "string",
            metageneration: "string",
            storageClass: "string",
            name: "string",
            size: "string",
            timeCreated: "string",
            updated: "string",
            id: "string",
            kind: "string",
            mediaLink: "string",
            selfLink: "string",
            timeStorageClassUpdated: "string",
          });
        });

        it("should allow setting of optional metadata", async () => {
          await testBucket.upload(smallFilePath);
          const [metadata] = await testBucket
            .file(smallFilePath.split("/").slice(-1)[0])
            .setMetadata({ cacheControl: "no-cache", contentLanguage: "en" });

          const metadataTypes: { [s: string]: string } = {};

          for (const key in metadata) {
            if (metadata[key]) {
              metadataTypes[key] = typeof metadata[key];
            }
          }

          expect(metadata.cacheControl).to.equal("no-cache");
          expect(metadata.contentLanguage).to.equal("en");
        });

        it("should allow fields under .metadata", async () => {
          await testBucket.upload(smallFilePath);
          const [metadata] = await testBucket
            .file(smallFilePath.split("/").slice(-1)[0])
            .setMetadata({ metadata: { is_over: "9000" } });

          expect(metadata.metadata.is_over).to.equal("9000");
        });

        it("should ignore any unknown fields", async () => {
          await testBucket.upload(smallFilePath);
          const [metadata] = await testBucket
            .file(smallFilePath.split("/").slice(-1)[0])
            .setMetadata({ nada: "true" });

          expect(metadata.nada).to.be.undefined;
        });
      });
    });

    after(async () => {
      if (tmpDir) {
        fs.unlinkSync(smallFilePath);
        fs.unlinkSync(largeFilePath);
        fs.rmdirSync(tmpDir);
      }

      if (!TEST_CONFIG.useProductionServers) {
        delete process.env.STORAGE_EMULATOR_HOST;
        await test.stopEmulators();
      }
    });
  });