Go语言 如何从Gcloud beta模拟器firestore中删除所有文档?

nlejzf6q  于 2023-03-10  发布在  Go
关注(0)|答案(4)|浏览(186)

我正在创建一些琐碎的应用程序来学习Firestore。
我使用以下命令启动了本地Firestore模拟器:
$ gcloud测试版模拟器firestore启动
启动模拟器后,我使用“go test”运行测试
我用数据填充了Firestore,并创建了一个函数来查询添加的一些记录/文档。
我从我的应用程序中删除了一些文档,但它们继续显示在查询中。
我试过:

  • 使用ctrl-c和ctrldd停止
  • $ gcloud测试版模拟器firestore停止
  • 我重新启动了MacBook,但文档仍然存在。

我不明白重启计算机后数据存储是如何持久化的,我猜数据存储在JSON文件或类似的文件中。
我搜索了一下,但没有找到任何关于模拟器的文档。

我是否应该启动模拟器,然后对模拟的Firestore运行测试?
如何刷新Firestore?

esbemjvw

esbemjvw1#

模拟器支持端点清除数据库(docs):

curl -v -X DELETE "http://localhost:PORT/emulator/v1/projects/PROJECT_NAME/databases/(default)/documents"

填写PORTPROJECT_NAME

nhn9ugyo

nhn9ugyo2#

因为您使用的是Go语言,所以我实现了一个小的测试助手,它可以帮助您启动模拟器、等待模拟器启动、清除现有数据、初始化客户端以及在模拟器完成后关闭操作器。
它使用了Juan答案中的技巧(您应该将其标记为答案)。
要使用此实用程序,您只需说:

client := startFirestoreEmulator(t)

源代码:

// Copyright 2021 Ahmet Alp Balkan
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package firestoretestutil contains test utilities for starting a firestore
// emulator locally for unit tests.
package firestoretestutil

import (
    "bytes"
    "context"
    "fmt"
    "net"
    "net/http"
    "os"
    "os/exec"
    "sync"
    "testing"
    "time"

    firestore "cloud.google.com/go/firestore"
)

const firestoreEmulatorProj = "dummy-emulator-firestore-project"

// cBuffer is a buffer safe for concurrent use.
type cBuffer struct {
    b bytes.Buffer
    sync.Mutex
}

func (c *cBuffer) Write(p []byte) (n int, err error) {
    c.Lock()
    defer c.Unlock()
    return c.b.Write(p)
}

func StartEmulator(t *testing.T, ctx context.Context) *firestore.Client {
    t.Helper()
    port := "8010"
    addr := "localhost:" + port
    ctx, cancel := context.WithCancel(ctx)
    t.Cleanup(func() {
        t.Log("shutting down firestore operator")
        cancel()
    })

    // TODO investigate why there are still java processes hanging around
    // despite we kill the exec'd command, suspecting /bin/bash wrapper that gcloud
    // applies around the java process.
    cmd := exec.CommandContext(ctx, "gcloud", "beta", "emulators", "firestore", "start", "--host-port="+addr)
    out := &cBuffer{b: bytes.Buffer{}}
    cmd.Stderr, cmd.Stdout = out, out
    if err := cmd.Start(); err != nil {
        t.Fatalf("failed to start firestore emulator: %v -- out:%s", err, out.b.String())
    }
    dialCtx, clean := context.WithTimeout(ctx, time.Second*10)
    defer clean()
    var connected bool
    for !connected {
        select {
        case <-dialCtx.Done():
            t.Fatalf("emulator did not come up timely: %v -- output: %s", dialCtx.Err(), out.b.String())
        default:
            c, err := (&net.Dialer{Timeout: time.Millisecond * 200}).DialContext(ctx, "tcp", addr)
            if err == nil {
                c.Close()
                t.Log("firestore emulator started")
                connected = true
                break
            }
            time.Sleep(time.Millisecond * 200) //before retrying
        }
    }
    os.Setenv("FIRESTORE_EMULATOR_HOST", addr)
    cl, err := firestore.NewClient(ctx, firestoreEmulatorProj)
    if err != nil {
        t.Fatal(err)
    }
    os.Unsetenv("FIRESTORE_EMULATOR_HOST")
    truncateDB(t, addr)
    return cl
}

func truncateDB(t *testing.T, addr string) {
    t.Helper()
    // technique adopted from https://stackoverflow.com/a/58866194/54929
    req, _ := http.NewRequest(http.MethodDelete, fmt.Sprintf("http://%s/emulator/v1/projects/%s/databases/(default)/documents",
        addr, firestoreEmulatorProj), nil)
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        t.Fatal(err)
    }
    if resp.StatusCode != http.StatusOK {
        t.Fatalf("failed to clear db: %v", resp.Status)
    }
}
tf7tbtn2

tf7tbtn23#

您可以使用以下命令:

module.exports.teardown = async () => {
  Promise.all(firebase.apps().map(app => app.delete()));
};

现在,每次调用teardown时,都将删除Firestore模拟器中的所有数据。

ddhy6vgd

ddhy6vgd4#

这没有文档说明,但这里是EmulatorsUI使用的端点。我的示例使用fetch发送请求,并假设您使用默认端口。
1.从Firestore模拟器中删除所有数据:

fetch('http://127.0.0.1:8080/emulator/v1/projects/PROJECT_NAME/databases/(default)/documents', { method: 'DELETE' })

1.从Auth模拟器中删除所有帐户:

fetch('http://127.0.0.1:9099/emulator/v1/projects/PROJECT_NAME/accounts', { method: 'DELETE' })

1.从存储存储桶中删除所有文件比较复杂,请使用此函数:

async function clearStorage() {
  const baseURL = 'http://127.0.0.1:9199/';
  const { items: buckets } = await (await fetch(baseURL + 'b')).json();
  const deleteRequests = [];
  for (const { id } of buckets) {
    const bucketURL = `${baseURL}v0/b/${id}/o/`;
    const { items } = await (await fetch(bucketURL)).json();
    for (const { name } of items) {
      const deleteURL = bucketURL + encodeURIComponent(name);
      deleteRequests.push(fetch(deleteURL, { method: 'DELETE' }));
    }
  }
  return Promise.all(deleteRequests);
}

为了给出一个完整的示例,下面是我的jest设置的代码:

const PROJECT_NAME = 'some-firebase-project' // replace with your project name

beforeEach(async () => {
  await clearAllEmulatorsData();
});

async function clearAllEmulatorsData() {
  // This is not officially documented approach and can eventually fail.
  return Promise.all([clearAuth(), clearFirestore(), clearStorage()]);
}

async function clearAuth() {
  const deleteURL = `http://127.0.0.1:9099/emulator/v1/projects/${PROJECT_NAME}/accounts`;
  return fetch(deleteURL, { method: 'DELETE' });
}

async function clearFirestore() {
  const deleteURL = `http://127.0.0.1:8080/emulator/v1/projects/${PROJECT_NAME}/databases/(default)/documents`;
  return fetch(deleteURL, { method: 'DELETE' });
}

async function clearStorage() {
  const baseURL = 'http://127.0.0.1:9199/';
  const { items: buckets } = await (await fetch(baseURL + 'b')).json();
  const deleteRequests = [];
  for (const { id } of buckets) {
    const bucketURL = `${baseURL}v0/b/${id}/o/`;
    const { items } = await (await fetch(bucketURL)).json();
    for (const { name } of items) {
      const deleteURL = bucketURL + encodeURIComponent(name);
      deleteRequests.push(fetch(deleteURL, { method: 'DELETE' }));
    }
  }
  return Promise.all(deleteRequests);
}

相关问题