mirror of
https://github.com/kennethnym/aris.git
synced 2026-04-16 06:41:17 +01:00
Compare commits
1 Commits
fix/creden
...
fix/upsert
| Author | SHA1 | Date | |
|---|---|---|---|
|
dcabc4fbdb
|
@@ -44,6 +44,15 @@ function getEnabledSourceIds(userId: string): string[] {
|
|||||||
*/
|
*/
|
||||||
let mockFindResult: unknown | undefined
|
let mockFindResult: unknown | undefined
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spy for `upsertConfig` calls. Tests can inspect calls via
|
||||||
|
* `mockUpsertConfigCalls`.
|
||||||
|
*/
|
||||||
|
const mockUpsertConfigCalls: Array<{
|
||||||
|
sourceId: string
|
||||||
|
data: { enabled: boolean; config: unknown }
|
||||||
|
}> = []
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Spy for `updateCredentials` calls. Tests can inspect calls via
|
* Spy for `updateCredentials` calls. Tests can inspect calls via
|
||||||
* `mockUpdateCredentialsCalls` or override behavior.
|
* `mockUpdateCredentialsCalls` or override behavior.
|
||||||
@@ -81,6 +90,9 @@ mock.module("../sources/user-sources.ts", () => ({
|
|||||||
updatedAt: now,
|
updatedAt: now,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async upsertConfig(sourceId: string, data: { enabled: boolean; config: unknown }) {
|
||||||
|
mockUpsertConfigCalls.push({ sourceId, data })
|
||||||
|
},
|
||||||
async updateCredentials(sourceId: string, credentials: Buffer) {
|
async updateCredentials(sourceId: string, credentials: Buffer) {
|
||||||
if (mockUpdateCredentialsError) {
|
if (mockUpdateCredentialsError) {
|
||||||
throw mockUpdateCredentialsError
|
throw mockUpdateCredentialsError
|
||||||
@@ -138,6 +150,7 @@ const weatherProvider: FeedSourceProvider = {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
enabledByUser.clear()
|
enabledByUser.clear()
|
||||||
mockFindResult = undefined
|
mockFindResult = undefined
|
||||||
|
mockUpsertConfigCalls.length = 0
|
||||||
mockUpdateCredentialsCalls.length = 0
|
mockUpdateCredentialsCalls.length = 0
|
||||||
mockUpdateCredentialsError = null
|
mockUpdateCredentialsError = null
|
||||||
})
|
})
|
||||||
@@ -806,31 +819,6 @@ describe("UserSessionManager.updateSourceCredentials", () => {
|
|||||||
expect(receivedCredentials).toEqual({ token: "refreshed" })
|
expect(receivedCredentials).toEqual({ token: "refreshed" })
|
||||||
})
|
})
|
||||||
|
|
||||||
test("adds source to session when source is enabled but not yet in session", async () => {
|
|
||||||
// Simulate a source that was never added to the session (e.g. credentials
|
|
||||||
// were missing at config time), but is enabled in the DB.
|
|
||||||
setEnabledSources([]) // no sources during session creation
|
|
||||||
const factory = mock(async () => createStubSource("test"))
|
|
||||||
const provider: FeedSourceProvider = { sourceId: "test", feedSourceForUser: factory }
|
|
||||||
const manager = new UserSessionManager({
|
|
||||||
db: fakeDb,
|
|
||||||
providers: [provider],
|
|
||||||
credentialEncryptor: testEncryptor,
|
|
||||||
})
|
|
||||||
|
|
||||||
const session = await manager.getOrCreate("user-1")
|
|
||||||
// Source is NOT in the session
|
|
||||||
expect(session.hasSource("test")).toBe(false)
|
|
||||||
|
|
||||||
// mockFindResult returns an enabled row by default, so the source
|
|
||||||
// row exists and is enabled in the DB.
|
|
||||||
await manager.updateSourceCredentials("user-1", "test", { token: "new-token" })
|
|
||||||
|
|
||||||
// Source should now be added to the session
|
|
||||||
expect(session.hasSource("test")).toBe(true)
|
|
||||||
expect(factory).toHaveBeenCalledTimes(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
test("persists credentials without session refresh when no active session", async () => {
|
test("persists credentials without session refresh when no active session", async () => {
|
||||||
setEnabledSources(["test"])
|
setEnabledSources(["test"])
|
||||||
const factory = mock(async () => createStubSource("test"))
|
const factory = mock(async () => createStubSource("test"))
|
||||||
@@ -849,3 +837,65 @@ describe("UserSessionManager.updateSourceCredentials", () => {
|
|||||||
expect(factory).not.toHaveBeenCalled()
|
expect(factory).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("UserSessionManager.upsertSourceConfig", () => {
|
||||||
|
test("persists config to DB even when feedSourceForUser throws", async () => {
|
||||||
|
setEnabledSources(["test"])
|
||||||
|
let callCount = 0
|
||||||
|
const factory = mock(async (_userId: string, _config: unknown, _credentials: unknown) => {
|
||||||
|
callCount++
|
||||||
|
// Succeed on first call (session creation), throw on second (upsert refresh)
|
||||||
|
if (callCount > 1) {
|
||||||
|
throw new InvalidSourceCredentialsError("test", "credentials required")
|
||||||
|
}
|
||||||
|
return createStubSource("test")
|
||||||
|
})
|
||||||
|
const provider: FeedSourceProvider = { sourceId: "test", feedSourceForUser: factory }
|
||||||
|
const manager = new UserSessionManager({ db: fakeDb, providers: [provider] })
|
||||||
|
|
||||||
|
// Create a session so the session-refresh path is exercised
|
||||||
|
await manager.getOrCreate("user-1")
|
||||||
|
|
||||||
|
const spy = spyOn(console, "warn").mockImplementation(() => {})
|
||||||
|
|
||||||
|
// upsertSourceConfig with no existing credentials — provider will throw
|
||||||
|
await manager.upsertSourceConfig("user-1", "test", {
|
||||||
|
enabled: true,
|
||||||
|
config: { url: "https://example.com" },
|
||||||
|
})
|
||||||
|
|
||||||
|
// Config should still have been persisted to DB
|
||||||
|
expect(mockUpsertConfigCalls).toHaveLength(1)
|
||||||
|
expect(mockUpsertConfigCalls[0]!.sourceId).toBe("test")
|
||||||
|
expect(mockUpsertConfigCalls[0]!.data.enabled).toBe(true)
|
||||||
|
|
||||||
|
// The error should have been logged, not thrown
|
||||||
|
expect(spy).toHaveBeenCalled()
|
||||||
|
|
||||||
|
spy.mockRestore()
|
||||||
|
})
|
||||||
|
|
||||||
|
test("adds source to session when feedSourceForUser succeeds", async () => {
|
||||||
|
setEnabledSources(["test"])
|
||||||
|
const factory = mock(async () => createStubSource("test"))
|
||||||
|
const provider: FeedSourceProvider = { sourceId: "test", feedSourceForUser: factory }
|
||||||
|
const manager = new UserSessionManager({ db: fakeDb, providers: [provider] })
|
||||||
|
|
||||||
|
const session = await manager.getOrCreate("user-1")
|
||||||
|
await manager.upsertSourceConfig("user-1", "test", { enabled: true })
|
||||||
|
|
||||||
|
// Config persisted
|
||||||
|
expect(mockUpsertConfigCalls).toHaveLength(1)
|
||||||
|
// Source should be in the session (feedSourceForUser succeeded)
|
||||||
|
expect(session.getSource("test")).toBeDefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
test("throws SourceNotFoundError for unknown provider", async () => {
|
||||||
|
setEnabledSources([])
|
||||||
|
const manager = new UserSessionManager({ db: fakeDb, providers: [] })
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
manager.upsertSourceConfig("user-1", "unknown", { enabled: true }),
|
||||||
|
).rejects.toBeInstanceOf(SourceNotFoundError)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
@@ -210,11 +210,22 @@ export class UserSessionManager {
|
|||||||
const credentials = existingRow?.credentials
|
const credentials = existingRow?.credentials
|
||||||
? this.decryptCredentials(existingRow.credentials)
|
? this.decryptCredentials(existingRow.credentials)
|
||||||
: null
|
: null
|
||||||
const source = await provider.feedSourceForUser(userId, config, credentials)
|
try {
|
||||||
if (session.hasSource(sourceId)) {
|
const source = await provider.feedSourceForUser(userId, config, credentials)
|
||||||
session.replaceSource(sourceId, source)
|
if (session.hasSource(sourceId)) {
|
||||||
} else {
|
session.replaceSource(sourceId, source)
|
||||||
session.addSource(source)
|
} else {
|
||||||
|
session.addSource(source)
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
// Provider may fail when credentials are not yet available (e.g. new
|
||||||
|
// source added before updateSourceCredentials is called). The config
|
||||||
|
// is already persisted above; updateSourceCredentials will add the
|
||||||
|
// source to the session later.
|
||||||
|
console.warn(
|
||||||
|
`[UserSessionManager] feedSourceForUser("${sourceId}") failed during upsert for user ${userId}, skipping session update:`,
|
||||||
|
err,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -249,15 +260,11 @@ export class UserSessionManager {
|
|||||||
// the DB already has the new credentials but the session keeps the old
|
// the DB already has the new credentials but the session keeps the old
|
||||||
// source. The next session creation will pick up the persisted credentials.
|
// source. The next session creation will pick up the persisted credentials.
|
||||||
const session = this.sessions.get(userId)
|
const session = this.sessions.get(userId)
|
||||||
if (session) {
|
if (session && session.hasSource(sourceId)) {
|
||||||
const row = await sources(this.db, userId).find(sourceId)
|
const row = await sources(this.db, userId).find(sourceId)
|
||||||
if (row?.enabled) {
|
if (row?.enabled) {
|
||||||
const source = await provider.feedSourceForUser(userId, row.config ?? {}, credentials)
|
const source = await provider.feedSourceForUser(userId, row.config ?? {}, credentials)
|
||||||
if (session.hasSource(sourceId)) {
|
session.replaceSource(sourceId, source)
|
||||||
session.replaceSource(sourceId, source)
|
|
||||||
} else {
|
|
||||||
session.addSource(source)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user