Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions server/geoip-refresh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ export interface RefreshDeps {
dir: string;
now: Date;
getMaxmindKey: () => Promise<{ key: string | null; source: MaxmindKeySource }>;
mkdir: (dir: string) => Promise<void>;
download: (url: string) => Promise<Buffer>;
gunzip: (data: Buffer) => Promise<Buffer>;
extractTarGz: (data: Buffer, editionId: string) => Promise<Buffer>;
Expand Down Expand Up @@ -228,6 +229,11 @@ function defaultDeps(): RefreshDeps {
dir: GEOIP_DIR,
now: new Date(),
getMaxmindKey,
// recursive:true is a no-op (not an error) when the dir already exists —
// no separate existence check needed. Fresh containers (Coolify's
// ephemeral filesystem) have no GEOIP_DB_DIR yet, and the very first
// refresh used to ENOENT trying to write its .tmp-*.mmdb file there.
mkdir: (dir) => fsp.mkdir(dir, { recursive: true }).then(() => {}),
download: downloadDefault,
gunzip: gunzipDefault,
extractTarGz: extractTarGzDefault,
Expand Down Expand Up @@ -262,6 +268,7 @@ export async function refreshGeoipDatabases(overrides: Partial<RefreshDeps> = {}
const at = deps.now.toISOString();
let source: GeoipSource = "dbip";
try {
await deps.mkdir(deps.dir);
const { key: maxmindKey } = await deps.getMaxmindKey();
source = resolveGeoipSource(maxmindKey);
const urls = buildDownloadUrls(source, deps.now);
Expand Down
36 changes: 36 additions & 0 deletions tests/geoip-refresh.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { describe, it, expect, vi, afterEach } from "vitest";
import { promises as fsp } from "fs";
import { rm } from "fs/promises";
import path from "path";
import { tmpdir } from "os";
import {
resolveGeoipSource,
buildDownloadUrls,
Expand Down Expand Up @@ -200,6 +204,7 @@ describe("refreshGeoipDatabases", () => {
dir: "/fake/geoip",
now: new Date("2026-09-02T00:00:00Z"),
getMaxmindKey: vi.fn(async () => ({ key: null, source: null as const })), // dbip path by default
mkdir: vi.fn(async () => {}),
download: vi.fn(async () => Buffer.from("raw-bytes")),
gunzip: vi.fn(async (data: Buffer) => Buffer.concat([Buffer.from("decompressed:"), data])),
extractTarGz: vi.fn(async () => Buffer.from("extracted-mmdb-bytes")),
Expand Down Expand Up @@ -353,4 +358,35 @@ describe("refreshGeoipDatabases", () => {
expect(rename).not.toHaveBeenCalled();
expect(deps.reload).not.toHaveBeenCalled();
});

it("a fresh container (GEOIP_DB_DIR does not exist yet) still succeeds — the dir is created before the first temp-file write", async () => {
// Reproduces prod: a brand-new container has no GEOIP_DB_DIR, and the very
// first refresh used to ENOENT trying to write the .tmp-*.mmdb file into a
// directory that was never created. Uses the REAL fs for writeFile/rename/
// unlink (not the jest-mock versions makeDeps() gives every other test in
// this file) so this test exercises the actual filesystem write path, not
// a mock's assumption that the directory is already there.
const dir = path.join(tmpdir(), `vox-geoip-mkdir-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
await expect(fsp.access(dir)).rejects.toThrow(); // sanity: must not pre-exist

try {
const deps = makeDeps({
dir,
mkdir: (d) => fsp.mkdir(d, { recursive: true }).then(() => {}),
writeFile: (p, data) => fsp.writeFile(p, data),
rename: (from, to) => fsp.rename(from, to),
unlink: (p) => fsp.unlink(p).then(() => {}, () => {}),
});

const result = await refreshGeoipDatabases(deps);

expect(result.ok).toBe(true);
const stat = await fsp.stat(dir);
expect(stat.isDirectory()).toBe(true);
const entries = (await fsp.readdir(dir)).sort();
expect(entries).toEqual(["ASN.mmdb", "City.mmdb", "geoip-meta.json"]);
} finally {
await rm(dir, { recursive: true, force: true });
}
});
});
Loading