mirror of
https://github.com/kennethnym/aris.git
synced 2026-03-24 02:51:17 +00:00
Manage per-user WeatherSource instances via FeedSourceProvider, following the same pattern as LocationService. Wire into FeedEngineService so weather data is included in the feed. Co-authored-by: Ona <no-reply@ona.com>
117 lines
3.6 KiB
TypeScript
117 lines
3.6 KiB
TypeScript
import type { Context } from "@aris/core"
|
|
|
|
import { LocationKey } from "@aris/source-location"
|
|
import {
|
|
Units,
|
|
WeatherFeedItemType,
|
|
type WeatherKitClient,
|
|
type WeatherKitResponse,
|
|
} from "@aris/source-weatherkit"
|
|
import { describe, expect, test } from "bun:test"
|
|
|
|
import fixture from "../../../../packages/aris-source-weatherkit/fixtures/san-francisco.json"
|
|
import { WeatherService } from "./service.ts"
|
|
|
|
const mockClient = createMockClient(fixture.response as WeatherKitResponse)
|
|
|
|
function createMockClient(response: WeatherKitResponse): WeatherKitClient {
|
|
return {
|
|
fetch: async () => response,
|
|
}
|
|
}
|
|
|
|
function createMockContext(location?: { lat: number; lng: number }): Context {
|
|
const ctx: Context = { time: new Date("2026-01-17T00:00:00Z") }
|
|
if (location) {
|
|
ctx[LocationKey] = { ...location, accuracy: 10, timestamp: new Date() }
|
|
}
|
|
return ctx
|
|
}
|
|
|
|
describe("WeatherService", () => {
|
|
test("feedSourceForUser creates source on first call", () => {
|
|
const service = new WeatherService({ client: mockClient })
|
|
const source = service.feedSourceForUser("user-1")
|
|
|
|
expect(source).toBeDefined()
|
|
expect(source.id).toBe("weather")
|
|
})
|
|
|
|
test("feedSourceForUser returns same source for same user", () => {
|
|
const service = new WeatherService({ client: mockClient })
|
|
const source1 = service.feedSourceForUser("user-1")
|
|
const source2 = service.feedSourceForUser("user-1")
|
|
|
|
expect(source1).toBe(source2)
|
|
})
|
|
|
|
test("feedSourceForUser returns different sources for different users", () => {
|
|
const service = new WeatherService({ client: mockClient })
|
|
const source1 = service.feedSourceForUser("user-1")
|
|
const source2 = service.feedSourceForUser("user-2")
|
|
|
|
expect(source1).not.toBe(source2)
|
|
})
|
|
|
|
test("feedSourceForUser applies hourly and daily limits", async () => {
|
|
const service = new WeatherService({
|
|
client: mockClient,
|
|
hourlyLimit: 3,
|
|
dailyLimit: 2,
|
|
})
|
|
const source = service.feedSourceForUser("user-1")
|
|
const context = createMockContext({ lat: 37.7749, lng: -122.4194 })
|
|
|
|
const items = await source.fetchItems(context)
|
|
|
|
const hourly = items.filter((i) => i.type === WeatherFeedItemType.hourly)
|
|
const daily = items.filter((i) => i.type === WeatherFeedItemType.daily)
|
|
|
|
expect(hourly).toHaveLength(3)
|
|
expect(daily).toHaveLength(2)
|
|
})
|
|
|
|
test("feedSourceForUser applies units", async () => {
|
|
const service = new WeatherService({
|
|
client: mockClient,
|
|
units: Units.imperial,
|
|
})
|
|
const source = service.feedSourceForUser("user-1")
|
|
const context = createMockContext({ lat: 37.7749, lng: -122.4194 })
|
|
|
|
const items = await source.fetchItems(context)
|
|
const current = items.find((i) => i.type === WeatherFeedItemType.current)
|
|
|
|
expect(current).toBeDefined()
|
|
// Fixture has ~15.87°C, imperial should be ~60.6°F
|
|
expect(current!.data.temperature).toBeGreaterThan(50)
|
|
})
|
|
|
|
test("removeUser removes the source", () => {
|
|
const service = new WeatherService({ client: mockClient })
|
|
service.feedSourceForUser("user-1")
|
|
|
|
service.removeUser("user-1")
|
|
|
|
// After removal, feedSourceForUser should create a new instance
|
|
const source2 = service.feedSourceForUser("user-1")
|
|
expect(source2).toBeDefined()
|
|
})
|
|
|
|
test("removeUser allows new source to be created", () => {
|
|
const service = new WeatherService({ client: mockClient })
|
|
const source1 = service.feedSourceForUser("user-1")
|
|
|
|
service.removeUser("user-1")
|
|
const source2 = service.feedSourceForUser("user-1")
|
|
|
|
expect(source1).not.toBe(source2)
|
|
})
|
|
|
|
test("removeUser is no-op for unknown user", () => {
|
|
const service = new WeatherService({ client: mockClient })
|
|
|
|
expect(() => service.removeUser("unknown")).not.toThrow()
|
|
})
|
|
})
|