fix(telemetry): validate activity state shape
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
import { afterEach, describe, expect, it } from "bun:test"
|
||||
import { mkdirSync, rmSync, writeFileSync } from "node:fs"
|
||||
import { join } from "node:path"
|
||||
import { tmpdir } from "node:os"
|
||||
|
||||
const originalXdgDataHome = process.env.XDG_DATA_HOME
|
||||
|
||||
function createDataHomePath(): string {
|
||||
return join(tmpdir(), `posthog-activity-state-${Date.now()}-${Math.random()}`)
|
||||
}
|
||||
|
||||
async function importPostHogActivityStateModule(): Promise<typeof import("./posthog-activity-state")> {
|
||||
return import(`./posthog-activity-state?test=${Date.now()}-${Math.random()}`)
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
if (originalXdgDataHome === undefined) {
|
||||
delete process.env.XDG_DATA_HOME
|
||||
} else {
|
||||
process.env.XDG_DATA_HOME = originalXdgDataHome
|
||||
}
|
||||
})
|
||||
|
||||
describe("getPostHogActivityCaptureState", () => {
|
||||
it("returns default state when activity file contains null", async () => {
|
||||
// given
|
||||
const dataHomePath = createDataHomePath()
|
||||
const cachePath = join(dataHomePath, "oh-my-opencode")
|
||||
mkdirSync(cachePath, { recursive: true })
|
||||
writeFileSync(join(cachePath, "posthog-activity.json"), "null\n")
|
||||
process.env.XDG_DATA_HOME = dataHomePath
|
||||
const { getPostHogActivityCaptureState } = await importPostHogActivityStateModule()
|
||||
|
||||
// when
|
||||
const result = getPostHogActivityCaptureState(new Date("2026-04-11T10:15:00.000Z"))
|
||||
|
||||
// then
|
||||
expect(result).toEqual({
|
||||
dayUTC: "2026-04-11",
|
||||
hourUTC: "2026-04-11T10",
|
||||
captureDaily: true,
|
||||
captureHourly: true,
|
||||
})
|
||||
|
||||
rmSync(dataHomePath, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
it("returns default state when activity file contains an array", async () => {
|
||||
// given
|
||||
const dataHomePath = createDataHomePath()
|
||||
const cachePath = join(dataHomePath, "oh-my-opencode")
|
||||
mkdirSync(cachePath, { recursive: true })
|
||||
writeFileSync(join(cachePath, "posthog-activity.json"), "[]\n")
|
||||
process.env.XDG_DATA_HOME = dataHomePath
|
||||
const { getPostHogActivityCaptureState } = await importPostHogActivityStateModule()
|
||||
|
||||
// when
|
||||
const result = getPostHogActivityCaptureState(new Date("2026-04-11T10:15:00.000Z"))
|
||||
|
||||
// then
|
||||
expect(result).toEqual({
|
||||
dayUTC: "2026-04-11",
|
||||
hourUTC: "2026-04-11T10",
|
||||
captureDaily: true,
|
||||
captureHourly: true,
|
||||
})
|
||||
|
||||
rmSync(dataHomePath, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
it("returns default state when activity file contains a number", async () => {
|
||||
// given
|
||||
const dataHomePath = createDataHomePath()
|
||||
const cachePath = join(dataHomePath, "oh-my-opencode")
|
||||
mkdirSync(cachePath, { recursive: true })
|
||||
writeFileSync(join(cachePath, "posthog-activity.json"), "42\n")
|
||||
process.env.XDG_DATA_HOME = dataHomePath
|
||||
const { getPostHogActivityCaptureState } = await importPostHogActivityStateModule()
|
||||
|
||||
// when
|
||||
const result = getPostHogActivityCaptureState(new Date("2026-04-11T10:15:00.000Z"))
|
||||
|
||||
// then
|
||||
expect(result).toEqual({
|
||||
dayUTC: "2026-04-11",
|
||||
hourUTC: "2026-04-11T10",
|
||||
captureDaily: true,
|
||||
captureHourly: true,
|
||||
})
|
||||
|
||||
rmSync(dataHomePath, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
it("reads valid activity state JSON", async () => {
|
||||
// given
|
||||
const dataHomePath = createDataHomePath()
|
||||
const cachePath = join(dataHomePath, "oh-my-opencode")
|
||||
mkdirSync(cachePath, { recursive: true })
|
||||
writeFileSync(
|
||||
join(cachePath, "posthog-activity.json"),
|
||||
`${JSON.stringify({
|
||||
lastActiveDayUTC: "2026-04-11",
|
||||
lastActiveHourUTC: "2026-04-11T10",
|
||||
})}\n`,
|
||||
)
|
||||
process.env.XDG_DATA_HOME = dataHomePath
|
||||
const { getPostHogActivityCaptureState } = await importPostHogActivityStateModule()
|
||||
|
||||
// when
|
||||
const result = getPostHogActivityCaptureState(new Date("2026-04-11T10:15:00.000Z"))
|
||||
|
||||
// then
|
||||
expect(result).toEqual({
|
||||
dayUTC: "2026-04-11",
|
||||
hourUTC: "2026-04-11T10",
|
||||
captureDaily: false,
|
||||
captureHourly: false,
|
||||
})
|
||||
|
||||
rmSync(dataHomePath, { recursive: true, force: true })
|
||||
})
|
||||
})
|
||||
@@ -32,6 +32,10 @@ function getUtcHourString(date: Date): string {
|
||||
return date.toISOString().slice(0, 13)
|
||||
}
|
||||
|
||||
function isPostHogActivityState(value: unknown): value is PostHogActivityState {
|
||||
return value !== null && typeof value === "object" && !Array.isArray(value)
|
||||
}
|
||||
|
||||
function readPostHogActivityState(): PostHogActivityState {
|
||||
const stateFilePath = getPostHogActivityStateFilePath()
|
||||
if (!existsSync(stateFilePath)) {
|
||||
@@ -40,7 +44,10 @@ function readPostHogActivityState(): PostHogActivityState {
|
||||
|
||||
try {
|
||||
const content = readFileSync(stateFilePath, "utf-8")
|
||||
const parsed = JSON.parse(content) as PostHogActivityState
|
||||
const parsed: unknown = JSON.parse(content)
|
||||
if (!isPostHogActivityState(parsed)) {
|
||||
return {}
|
||||
}
|
||||
return parsed
|
||||
} catch (error) {
|
||||
log("[posthog-activity-state] Failed to read activity state", {
|
||||
|
||||
Reference in New Issue
Block a user