You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

单元测试Firebase Storage安全规则时上传文件卡住如何排查

Firebase Storage规则测试上传卡住问题排查

问题背景

我配置了一个支持用户上传音频文件的存储桶,目标是验证其权限规则是否符合预期。但在使用@firebase/rules-unit-testing库向模拟器上传测试文件时,上传进程一直卡住无任何进展。

安全规则配置

rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    match /{allPaths=**} {
      allow read, write: if false;
    }
    match /audio/{audioFileID} {
      allow create: if request.auth != null && request.resource.contentType.matches('audio/(flac|wav)');
    }
  }
}

测试代码

import {
  assertFails,
  assertSucceeds,
  initializeTestEnvironment,
  RulesTestEnvironment,
} from "@firebase/rules-unit-testing";
import { test, beforeAll, beforeEach, afterAll } from "vitest";
import fs from "fs";

const createTestFile = (size: number) => Buffer.alloc(size);
let testEnv: RulesTestEnvironment;

beforeAll(async () => {
  testEnv = await initializeTestEnvironment({
    projectId: "stst-et-interviewer-dev",
    hub: {
      host: "localhost",
      port: 4400,
    },
  });
});

beforeEach(async () => {
  await testEnv.clearStorage();
  await testEnv.clearFirestore();
});

afterAll(async () => await testEnv.cleanup());

const loadStepStoneImage = () =>
  fs.readFileSync("./public/images/stepstoneLogo.svg");

test("Storage does not allow you to read files", async () => {
  const rouge = testEnv.unauthenticatedContext();
  const alice = testEnv.authenticatedContext("alice");
  await assertFails(
    rouge.storage().ref("audio/test-interview.flac").getDownloadURL()
  );
  await assertFails(
    alice.storage().ref("audio/test-interview.flac").getDownloadURL()
  );
  await assertFails(alice.storage().ref("audio/").listAll());
});

test("Storage does not allow you to upload a file if you are not logged in", async () => {
  const rouge = testEnv.unauthenticatedContext();
  const upload = rouge
    .storage("stst-et-interviewer-dev.appspot.com")
    .ref("audio/logo.svg")
    .put(createTestFile(200), { contentType: "audio/flac" });
  
  await assertFails(upload.then()); // <- 在此处卡住
  console.log("该日志不会打印");
});

可能原因

  • 模拟器配置缺失:初始化测试环境时仅配置了hub端口4400,未显式指定Storage模拟器配置,SDK无法正确路由Storage请求到对应服务端口(默认Storage模拟器端口为9199),导致请求一直挂起。
  • Promise写法错误:upload.then()未传入任何回调函数,返回的Promise永远不会进入决议状态,assertFails接收始终pending的Promise就会一直卡住。
  • 存储桶名称不匹配:测试代码中手动指定存储桶为stst-et-interviewer-dev.appspot.com,如果模拟器实际启动的默认存储桶名称和该值不一致,上传请求会被转发到不存在的桶,无法得到响应。
  • 版本兼容问题:本地安装的@firebase/rules-unit-testing版本和Firebase CLI版本差距过大,Storage模拟器的API逻辑和SDK调用逻辑不匹配,导致请求无法正常返回。

调试方法

  • 先访问Firebase模拟器控制台(默认地址http://localhost:4000),确认Storage模拟器是否正常启动,记录其运行端口和默认存储桶名称,核对测试代码中的配置是否一致。
  • 给上传任务添加catch回调和超时逻辑,跳过assertFails单独运行上传逻辑,获取明确的错误信息:
const upload = rouge
  .storage("stst-et-interviewer-dev.appspot.com")
  .ref("audio/logo.svg")
  .put(createTestFile(200), { contentType: "audio/flac" });

upload.then(res => console.log("上传成功", res)).catch(err => console.error("上传报错", err));
const timeout = new Promise((_, reject) => setTimeout(() => reject(new Error("请求超时")), 5000));
await Promise.race([upload, timeout]);
  • 初始化测试环境时显式配置Storage模拟器参数,不要仅依赖hub自动发现:
testEnv = await initializeTestEnvironment({
  projectId: "stst-et-interviewer-dev",
  hub: {
    host: "localhost",
    port: 4400,
  },
  storage: {
    host: "localhost",
    port: 9199
  }
});
  • 修正assertFails的入参,不要传入无回调的then调用,直接传入上传任务本身即可:await assertFails(upload)。
  • 先移除手动指定的存储桶参数,直接调用rouge.storage()使用默认存储桶做测试,排除桶名不匹配的问题。

内容的提问来源于stack exchange,提问作者Frank Weslien

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 21:48:10