File size: 4,889 Bytes
b01d539
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { DatabaseSync } from "node:sqlite";
import { afterEach, describe, expect, it, vi } from "vitest";
import { bootstrapLocalDatabase } from "./database-bootstrap.js";

const temporaryDirectories: string[] = [];

afterEach(async () => {
  await Promise.all(
    temporaryDirectories
      .splice(0)
      .map((directory) => rm(directory, { recursive: true, force: true })),
  );
});

describe("bootstrapLocalDatabase", () => {
  it("preserves a valid existing database without attempting restore", async () => {
    const root = await mkdtemp(join(tmpdir(), "oloka-bootstrap-"));
    temporaryDirectories.push(root);
    const databasePath = join(root, "local", "oloka.db");
    await mkdir(join(root, "local"));
    const database = new DatabaseSync(databasePath);
    database.exec("CREATE TABLE marker (value TEXT NOT NULL)");
    database.close();
    const restore = vi.fn();

    await expect(
      bootstrapLocalDatabase({
        databasePath,
        objectStorageRoot: join(root, "objects"),
        mode: "restore-required",
        restore,
      }),
    ).resolves.toEqual({ source: "existing" });
    expect(restore).not.toHaveBeenCalled();
  });

  it("allows a fresh database only when missing replicas are explicitly allowed", async () => {
    const root = await mkdtemp(join(tmpdir(), "oloka-bootstrap-"));
    temporaryDirectories.push(root);
    const restore = vi.fn().mockResolvedValue(undefined);

    await expect(
      bootstrapLocalDatabase({
        databasePath: join(root, "fresh", "oloka.db"),
        objectStorageRoot: join(root, "objects"),
        mode: "fresh-if-replica-missing",
        restore,
      }),
    ).resolves.toEqual({ source: "fresh" });
    expect(restore).toHaveBeenCalledOnce();
  });

  it("fails closed when restore-required finds no replica", async () => {
    const root = await mkdtemp(join(tmpdir(), "oloka-bootstrap-"));
    temporaryDirectories.push(root);

    await expect(
      bootstrapLocalDatabase({
        databasePath: join(root, "required", "oloka.db"),
        objectStorageRoot: join(root, "objects"),
        mode: "restore-required",
        restore: vi.fn().mockResolvedValue(undefined),
      }),
    ).rejects.toThrow(/required/i);
  });

  it.each([
    "network restore failed",
    "authentication restore failed",
    "corrupt replica restore failed",
  ])("fails closed on %s", async (message) => {
    const root = await mkdtemp(join(tmpdir(), "oloka-bootstrap-"));
    temporaryDirectories.push(root);
    const restore = vi.fn().mockRejectedValue(new Error(message));

    await expect(
      bootstrapLocalDatabase({
        databasePath: join(root, "failed", "oloka.db"),
        objectStorageRoot: join(root, "objects"),
        mode: "fresh-if-replica-missing",
        restore,
      }),
    ).rejects.toThrow(message);
  });

  it("accepts only an integrity-checked restored database", async () => {
    const root = await mkdtemp(join(tmpdir(), "oloka-bootstrap-"));
    temporaryDirectories.push(root);
    const databasePath = join(root, "restored", "oloka.db");
    const restore = vi.fn(() => {
      const database = new DatabaseSync(databasePath);
      database.exec("CREATE TABLE marker (value TEXT NOT NULL)");
      database.close();
      return Promise.resolve();
    });

    await expect(
      bootstrapLocalDatabase({
        databasePath,
        objectStorageRoot: join(root, "objects"),
        mode: "restore-required",
        restore,
      }),
    ).resolves.toEqual({ source: "restored" });
  });

  it("rejects a corrupt or zero-byte restore result", async () => {
    const root = await mkdtemp(join(tmpdir(), "oloka-bootstrap-"));
    temporaryDirectories.push(root);
    const databasePath = join(root, "corrupt", "oloka.db");

    await expect(
      bootstrapLocalDatabase({
        databasePath,
        objectStorageRoot: join(root, "objects"),
        mode: "restore-required",
        restore: () => writeFile(databasePath, new Uint8Array()),
      }),
    ).rejects.toThrow(/zero bytes/i);
  });

  it("rejects an unexpected rollback journal left by restore", async () => {
    const root = await mkdtemp(join(tmpdir(), "oloka-bootstrap-"));
    temporaryDirectories.push(root);
    const databasePath = join(root, "journal", "oloka.db");

    await expect(
      bootstrapLocalDatabase({
        databasePath,
        objectStorageRoot: join(root, "objects"),
        mode: "restore-required",
        restore: async () => {
          const database = new DatabaseSync(databasePath);
          database.exec("CREATE TABLE marker (value TEXT NOT NULL)");
          database.close();
          await writeFile(`${databasePath}-journal`, "unexpected");
        },
      }),
    ).rejects.toThrow(/journal/i);
  });
});