firebase-js-sdk icon indicating copy to clipboard operation
firebase-js-sdk copied to clipboard

rules-unit-testing, storage uploadBytes and uploadString never resolve

Open kris-kolve-jumio opened this issue 2 years ago • 7 comments

[REQUIRED] Describe your environment

  • Operating System version: macOS v12.4
  • Browser version: Chrom 104.0.5112.79 (not relevant testing in vsCode with mocha)
  • Firebase SDK version: 9.9.2
  • Firebase tools version: 2.0.4
  • Mocha version:10.0.0
  • Firebase-tools version: 11.6.0
  • Firebase Product: rules-unit-testing; storage

[REQUIRED] Describe the problem

uploadBytes and uploadString storage functions do not resolve when using the @firebase/rules-unit-testing library. This causes the test to timeout even at 20+seconds.

This occurs with authenticatedContext, and unauthenticated contexts.

It is also worth noting that, getBytes, and getDownloadURL, resolve as expected.

I get the same results using Mocha and Jest. I have even tried the v8 SDK with the same results.

I did not have the upload issue when using the Admin SDK with the emulators. I have not tried it yet in my app but suspect it will work as desired.

Steps to reproduce:

  1. Create a firebase project and install Mocha and rules-unit-testing along with SDK
  2. TestEnvironment: a. host:"127.0.0.1" b. port:
  3. Create a testing folder and add a spec file for the test.
  4. in the test, create an unauthenticated context,
  5. attempt to upload using uploadBytes.

Result: upload promise will never resolve.

Expected Result: upload promise resolves well within a second and the test will fail (assuming the rules prevent uploads from unauthenticated users.

If I do not set the hub host to "127.0.0.1" I will get an ECCONREFUSED ::1:4400. Also interesting and possibly related:

  1. Setting the hub:{host:"127.0.0.1", port:4400} does not make storage discoverable and also does not throw an ECCONREFUSED ::1. However, it does make it so the getBytes test ends up throwing a "FirebaseError: Firebase Storage: Max retry time for operation exceeded"

  2. Setting the storage:{host:"127.0.0.1", port:9199} seems to make the storage discoverable. getBytes will resolve but uploadBytes will never resolve... I wonder if the host configuration is not propagating to post commands sent to the emulators? IDK.

Relevant Code:

storage.rules -

rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    match /{allPaths=**} {

      function canRead(){
        return request.auth != null &&
          request.auth.token.email != null &&
          request.auth.token.email_verified ;
      }
      allow read: if canRead(); //ONLY ALLOW AUTHENTICATED USERS TO READ DATA
      allow write: if false; // Never allow writing
    }
  }
}

Note: it is necessary to add type:'module' in the package file so that imports work.

index.spec.js - TEST FILE

import * as fbTesting from "@firebase/rules-unit-testing"
import http from 'http'
import {readFileSync, createWriteStream} from 'fs'
// import firebase from 'firebase'
import assert from 'assert'
import { ref as storRef, deleteObject, uploadString, listAll, uploadBytes, getBytes } from "firebase/storage";
import 'mocha'

const {
  assertFails,
  assertSucceeds,
  initializeTestEnvironment,
} = fbTesting

const host = '127.0.0.1'
const port = 9199

/** 
   * @type {fbTesting.RulesTestEnvironment}
   */
let testEnv;

before(async ()=>{
  testEnv = await initializeTestEnvironment({
    projectId: 'demo-data-visualization',
    hub:{host, port:4400},
    storage:{ host,port:9199},
    
  })
  console.log("test Environment Created", testEnv.projectId, testEnv.emulators)
})

after("Cleaning up", async ()=>{
  await testEnv.cleanup()
})

describe("Testing the Storage Security Rules", function() {
  this.timeout(20000)

  describe("An Un-Authenticated user,", ()=>{

    /** @type {fbTesting.storage.Storage} */
    let alice
    before("Creating Unauth User",async ()=>{
      testEnv.clearStorage()
      const context = testEnv.unauthenticatedContext()
      alice = context.storage();
      // console.log(alice)
    })

    it("Can NOT read from storage", async ()=>{
      // console.log(alice)
      // THIS TEST WORKS
      const ref = storRef(alice, 'data.json')
      const blob = getBytes(ref)
      await assertFails(blob)
    })

    it("Can NOT upload a file", async ()=>{

      //THIS TEST TIMES OUT IN 20 seconds
      const ref = storRef(alice, 'data/test.json')
      const bytes = new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21]);
      const upload = uploadBytes(ref, bytes, {contentType:'application/octet-stream'})
      await assertFails(upload)
    })
  })

})

kris-kolve-jumio avatar Aug 13 '22 22:08 kris-kolve-jumio

I couldn't figure out how to label this issue, so I've labeled it for a human to triage. Hang tight.

google-oss-bot avatar Aug 13 '22 22:08 google-oss-bot

@jbalidiong I finished updating the ticket. Sorry for the delay.

kris-kolve-jumio avatar Aug 15 '22 16:08 kris-kolve-jumio

Any updates on the issue?

YehorPytomets avatar Jan 31 '23 11:01 YehorPytomets

@YehorPytomets I haven't heard anything but I have played around some more...

I tried moving to Jest. and interestingly with Jest 27.x I can get it to work as expected!

Jest 28/29.x I get the same issue. where uploads never resolve. So far I have been able to deduce that it has something to do with what libraries are injected when no browser is actually present. I think firebase Storage API is complicated by the fact that it relies on the GCP storage library. But this is out of my wheelhouse. Hopefully that makes some sense.

kris-kolve-jumio avatar Feb 06 '23 23:02 kris-kolve-jumio

Thank you @kris-kolve-jumio.

I was able to play around with this and fixed the upload issue by changing the storage host from localhost to 127.0.0.1. I use Mocha ^10.2.0.

YehorPytomets avatar Feb 07 '23 09:02 YehorPytomets

@kris-kolve-jumio I can confirm similar issue that jest 27 works but 28 and 29 do not. Also @YehorPytomets suggestion of switching to Mocha instead of jest did the trick for me. I could not use jest27 because I was using more bleeding-edge esm features that jest27 was too old for

daniel-sudz avatar Mar 29 '23 01:03 daniel-sudz

Hello, any updates on this? The issue is still relevant even with @firebase/[email protected]. We have had to split our tests to ones without uploads running on vitest with the rest stuck on [email protected].

It seems like the uploadBytes function gets stuck after it opens a connection to the emulators. The logs do not contain any information about the upload until vitest stops the test after timeout. This is the only log that gets printed:

BadRequestError: request aborted
    at IncomingMessage.onAborted (/.../node_modules/raw-body/index.js:245:10)
    at IncomingMessage.emit (node:events:514:28)
    at IncomingMessage.emit (node:domain:489:12)
    at IncomingMessage._destroy (node:_http_incoming:224:10)
    at _destroy (node:internal/streams/destroy:109:10)
    at IncomingMessage.destroy (node:internal/streams/destroy:71:5)
    at abortIncoming (node:_http_server:782:9)
    at socketOnClose (node:_http_server:776:3)
    at Socket.emit (node:events:526:35)
    at Socket.emit (node:domain:489:12)

Running the upload with uploadBytesResumable returns only a single running snapshot that reports bytesTransfered as 0.

Security rules seem to have no effect - if I disable them all, the result of the upload is still the same.

portrik avatar Apr 23 '24 14:04 portrik