mirror of
https://gitea.toothfairyai.com/ToothFairyAI/tf_code.git
synced 2026-04-09 18:29:39 +00:00
refactor: apply minimal tfcode branding
- Rename packages/opencode → packages/tfcode (directory only) - Rename bin/opencode → bin/tfcode (CLI binary) - Rename .opencode → .tfcode (config directory) - Update package.json name and bin field - Update config directory path references (.tfcode) - Keep internal code references as 'opencode' for easy upstream sync - Keep @opencode-ai/* workspace package names This minimal branding approach allows clean merges from upstream opencode repository while providing tfcode branding for users.
This commit is contained in:
326
packages/tfcode/test/account/repo.test.ts
Normal file
326
packages/tfcode/test/account/repo.test.ts
Normal file
@@ -0,0 +1,326 @@
|
||||
import { expect } from "bun:test"
|
||||
import { Effect, Layer, Option } from "effect"
|
||||
|
||||
import { AccountRepo } from "../../src/account/repo"
|
||||
import { AccessToken, AccountID, OrgID, RefreshToken } from "../../src/account/schema"
|
||||
import { Database } from "../../src/storage/db"
|
||||
import { testEffect } from "../lib/effect"
|
||||
|
||||
const truncate = Layer.effectDiscard(
|
||||
Effect.sync(() => {
|
||||
const db = Database.Client()
|
||||
db.run(/*sql*/ `DELETE FROM account_state`)
|
||||
db.run(/*sql*/ `DELETE FROM account`)
|
||||
}),
|
||||
)
|
||||
|
||||
const it = testEffect(Layer.merge(AccountRepo.layer, truncate))
|
||||
|
||||
it.effect("list returns empty when no accounts exist", () =>
|
||||
Effect.gen(function* () {
|
||||
const accounts = yield* AccountRepo.use((r) => r.list())
|
||||
expect(accounts).toEqual([])
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("active returns none when no accounts exist", () =>
|
||||
Effect.gen(function* () {
|
||||
const active = yield* AccountRepo.use((r) => r.active())
|
||||
expect(Option.isNone(active)).toBe(true)
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("persistAccount inserts and getRow retrieves", () =>
|
||||
Effect.gen(function* () {
|
||||
const id = AccountID.make("user-1")
|
||||
yield* AccountRepo.use((r) =>
|
||||
r.persistAccount({
|
||||
id,
|
||||
email: "test@example.com",
|
||||
url: "https://control.example.com",
|
||||
accessToken: AccessToken.make("at_123"),
|
||||
refreshToken: RefreshToken.make("rt_456"),
|
||||
expiry: Date.now() + 3600_000,
|
||||
orgID: Option.some(OrgID.make("org-1")),
|
||||
}),
|
||||
)
|
||||
|
||||
const row = yield* AccountRepo.use((r) => r.getRow(id))
|
||||
expect(Option.isSome(row)).toBe(true)
|
||||
const value = Option.getOrThrow(row)
|
||||
expect(value.id).toBe(AccountID.make("user-1"))
|
||||
expect(value.email).toBe("test@example.com")
|
||||
|
||||
const active = yield* AccountRepo.use((r) => r.active())
|
||||
expect(Option.getOrThrow(active).active_org_id).toBe(OrgID.make("org-1"))
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("persistAccount sets the active account and org", () =>
|
||||
Effect.gen(function* () {
|
||||
const id1 = AccountID.make("user-1")
|
||||
const id2 = AccountID.make("user-2")
|
||||
|
||||
yield* AccountRepo.use((r) =>
|
||||
r.persistAccount({
|
||||
id: id1,
|
||||
email: "first@example.com",
|
||||
url: "https://control.example.com",
|
||||
accessToken: AccessToken.make("at_1"),
|
||||
refreshToken: RefreshToken.make("rt_1"),
|
||||
expiry: Date.now() + 3600_000,
|
||||
orgID: Option.some(OrgID.make("org-1")),
|
||||
}),
|
||||
)
|
||||
|
||||
yield* AccountRepo.use((r) =>
|
||||
r.persistAccount({
|
||||
id: id2,
|
||||
email: "second@example.com",
|
||||
url: "https://control.example.com",
|
||||
accessToken: AccessToken.make("at_2"),
|
||||
refreshToken: RefreshToken.make("rt_2"),
|
||||
expiry: Date.now() + 3600_000,
|
||||
orgID: Option.some(OrgID.make("org-2")),
|
||||
}),
|
||||
)
|
||||
|
||||
// Last persisted account is active with its org
|
||||
const active = yield* AccountRepo.use((r) => r.active())
|
||||
expect(Option.isSome(active)).toBe(true)
|
||||
expect(Option.getOrThrow(active).id).toBe(AccountID.make("user-2"))
|
||||
expect(Option.getOrThrow(active).active_org_id).toBe(OrgID.make("org-2"))
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("list returns all accounts", () =>
|
||||
Effect.gen(function* () {
|
||||
const id1 = AccountID.make("user-1")
|
||||
const id2 = AccountID.make("user-2")
|
||||
|
||||
yield* AccountRepo.use((r) =>
|
||||
r.persistAccount({
|
||||
id: id1,
|
||||
email: "a@example.com",
|
||||
url: "https://control.example.com",
|
||||
accessToken: AccessToken.make("at_1"),
|
||||
refreshToken: RefreshToken.make("rt_1"),
|
||||
expiry: Date.now() + 3600_000,
|
||||
orgID: Option.none(),
|
||||
}),
|
||||
)
|
||||
|
||||
yield* AccountRepo.use((r) =>
|
||||
r.persistAccount({
|
||||
id: id2,
|
||||
email: "b@example.com",
|
||||
url: "https://control.example.com",
|
||||
accessToken: AccessToken.make("at_2"),
|
||||
refreshToken: RefreshToken.make("rt_2"),
|
||||
expiry: Date.now() + 3600_000,
|
||||
orgID: Option.some(OrgID.make("org-1")),
|
||||
}),
|
||||
)
|
||||
|
||||
const accounts = yield* AccountRepo.use((r) => r.list())
|
||||
expect(accounts.length).toBe(2)
|
||||
expect(accounts.map((a) => a.email).sort()).toEqual(["a@example.com", "b@example.com"])
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("remove deletes an account", () =>
|
||||
Effect.gen(function* () {
|
||||
const id = AccountID.make("user-1")
|
||||
|
||||
yield* AccountRepo.use((r) =>
|
||||
r.persistAccount({
|
||||
id,
|
||||
email: "test@example.com",
|
||||
url: "https://control.example.com",
|
||||
accessToken: AccessToken.make("at_1"),
|
||||
refreshToken: RefreshToken.make("rt_1"),
|
||||
expiry: Date.now() + 3600_000,
|
||||
orgID: Option.none(),
|
||||
}),
|
||||
)
|
||||
|
||||
yield* AccountRepo.use((r) => r.remove(id))
|
||||
|
||||
const row = yield* AccountRepo.use((r) => r.getRow(id))
|
||||
expect(Option.isNone(row)).toBe(true)
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("use stores the selected org and marks the account active", () =>
|
||||
Effect.gen(function* () {
|
||||
const id1 = AccountID.make("user-1")
|
||||
const id2 = AccountID.make("user-2")
|
||||
|
||||
yield* AccountRepo.use((r) =>
|
||||
r.persistAccount({
|
||||
id: id1,
|
||||
email: "first@example.com",
|
||||
url: "https://control.example.com",
|
||||
accessToken: AccessToken.make("at_1"),
|
||||
refreshToken: RefreshToken.make("rt_1"),
|
||||
expiry: Date.now() + 3600_000,
|
||||
orgID: Option.none(),
|
||||
}),
|
||||
)
|
||||
|
||||
yield* AccountRepo.use((r) =>
|
||||
r.persistAccount({
|
||||
id: id2,
|
||||
email: "second@example.com",
|
||||
url: "https://control.example.com",
|
||||
accessToken: AccessToken.make("at_2"),
|
||||
refreshToken: RefreshToken.make("rt_2"),
|
||||
expiry: Date.now() + 3600_000,
|
||||
orgID: Option.none(),
|
||||
}),
|
||||
)
|
||||
|
||||
yield* AccountRepo.use((r) => r.use(id1, Option.some(OrgID.make("org-99"))))
|
||||
const active1 = yield* AccountRepo.use((r) => r.active())
|
||||
expect(Option.getOrThrow(active1).id).toBe(id1)
|
||||
expect(Option.getOrThrow(active1).active_org_id).toBe(OrgID.make("org-99"))
|
||||
|
||||
yield* AccountRepo.use((r) => r.use(id1, Option.none()))
|
||||
const active2 = yield* AccountRepo.use((r) => r.active())
|
||||
expect(Option.getOrThrow(active2).active_org_id).toBeNull()
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("persistToken updates token fields", () =>
|
||||
Effect.gen(function* () {
|
||||
const id = AccountID.make("user-1")
|
||||
|
||||
yield* AccountRepo.use((r) =>
|
||||
r.persistAccount({
|
||||
id,
|
||||
email: "test@example.com",
|
||||
url: "https://control.example.com",
|
||||
accessToken: AccessToken.make("old_token"),
|
||||
refreshToken: RefreshToken.make("old_refresh"),
|
||||
expiry: 1000,
|
||||
orgID: Option.none(),
|
||||
}),
|
||||
)
|
||||
|
||||
const expiry = Date.now() + 7200_000
|
||||
yield* AccountRepo.use((r) =>
|
||||
r.persistToken({
|
||||
accountID: id,
|
||||
accessToken: AccessToken.make("new_token"),
|
||||
refreshToken: RefreshToken.make("new_refresh"),
|
||||
expiry: Option.some(expiry),
|
||||
}),
|
||||
)
|
||||
|
||||
const row = yield* AccountRepo.use((r) => r.getRow(id))
|
||||
const value = Option.getOrThrow(row)
|
||||
expect(value.access_token).toBe(AccessToken.make("new_token"))
|
||||
expect(value.refresh_token).toBe(RefreshToken.make("new_refresh"))
|
||||
expect(value.token_expiry).toBe(expiry)
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("persistToken with no expiry sets token_expiry to null", () =>
|
||||
Effect.gen(function* () {
|
||||
const id = AccountID.make("user-1")
|
||||
|
||||
yield* AccountRepo.use((r) =>
|
||||
r.persistAccount({
|
||||
id,
|
||||
email: "test@example.com",
|
||||
url: "https://control.example.com",
|
||||
accessToken: AccessToken.make("old_token"),
|
||||
refreshToken: RefreshToken.make("old_refresh"),
|
||||
expiry: 1000,
|
||||
orgID: Option.none(),
|
||||
}),
|
||||
)
|
||||
|
||||
yield* AccountRepo.use((r) =>
|
||||
r.persistToken({
|
||||
accountID: id,
|
||||
accessToken: AccessToken.make("new_token"),
|
||||
refreshToken: RefreshToken.make("new_refresh"),
|
||||
expiry: Option.none(),
|
||||
}),
|
||||
)
|
||||
|
||||
const row = yield* AccountRepo.use((r) => r.getRow(id))
|
||||
expect(Option.getOrThrow(row).token_expiry).toBeNull()
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("persistAccount upserts on conflict", () =>
|
||||
Effect.gen(function* () {
|
||||
const id = AccountID.make("user-1")
|
||||
|
||||
yield* AccountRepo.use((r) =>
|
||||
r.persistAccount({
|
||||
id,
|
||||
email: "test@example.com",
|
||||
url: "https://control.example.com",
|
||||
accessToken: AccessToken.make("at_v1"),
|
||||
refreshToken: RefreshToken.make("rt_v1"),
|
||||
expiry: 1000,
|
||||
orgID: Option.some(OrgID.make("org-1")),
|
||||
}),
|
||||
)
|
||||
|
||||
yield* AccountRepo.use((r) =>
|
||||
r.persistAccount({
|
||||
id,
|
||||
email: "test@example.com",
|
||||
url: "https://control.example.com",
|
||||
accessToken: AccessToken.make("at_v2"),
|
||||
refreshToken: RefreshToken.make("rt_v2"),
|
||||
expiry: 2000,
|
||||
orgID: Option.some(OrgID.make("org-2")),
|
||||
}),
|
||||
)
|
||||
|
||||
const accounts = yield* AccountRepo.use((r) => r.list())
|
||||
expect(accounts.length).toBe(1)
|
||||
|
||||
const row = yield* AccountRepo.use((r) => r.getRow(id))
|
||||
const value = Option.getOrThrow(row)
|
||||
expect(value.access_token).toBe(AccessToken.make("at_v2"))
|
||||
|
||||
const active = yield* AccountRepo.use((r) => r.active())
|
||||
expect(Option.getOrThrow(active).active_org_id).toBe(OrgID.make("org-2"))
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("remove clears active state when deleting the active account", () =>
|
||||
Effect.gen(function* () {
|
||||
const id = AccountID.make("user-1")
|
||||
|
||||
yield* AccountRepo.use((r) =>
|
||||
r.persistAccount({
|
||||
id,
|
||||
email: "test@example.com",
|
||||
url: "https://control.example.com",
|
||||
accessToken: AccessToken.make("at_1"),
|
||||
refreshToken: RefreshToken.make("rt_1"),
|
||||
expiry: Date.now() + 3600_000,
|
||||
orgID: Option.some(OrgID.make("org-1")),
|
||||
}),
|
||||
)
|
||||
|
||||
yield* AccountRepo.use((r) => r.remove(id))
|
||||
|
||||
const active = yield* AccountRepo.use((r) => r.active())
|
||||
expect(Option.isNone(active)).toBe(true)
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("getRow returns none for nonexistent account", () =>
|
||||
Effect.gen(function* () {
|
||||
const row = yield* AccountRepo.use((r) => r.getRow(AccountID.make("nope")))
|
||||
expect(Option.isNone(row)).toBe(true)
|
||||
}),
|
||||
)
|
||||
282
packages/tfcode/test/account/service.test.ts
Normal file
282
packages/tfcode/test/account/service.test.ts
Normal file
@@ -0,0 +1,282 @@
|
||||
import { expect } from "bun:test"
|
||||
import { Duration, Effect, Layer, Option, Schema } from "effect"
|
||||
import { HttpClient, HttpClientResponse } from "effect/unstable/http"
|
||||
|
||||
import { AccountRepo } from "../../src/account/repo"
|
||||
import { Account } from "../../src/account"
|
||||
import { AccessToken, AccountID, DeviceCode, Login, Org, OrgID, RefreshToken, UserCode } from "../../src/account/schema"
|
||||
import { Database } from "../../src/storage/db"
|
||||
import { testEffect } from "../lib/effect"
|
||||
|
||||
const truncate = Layer.effectDiscard(
|
||||
Effect.sync(() => {
|
||||
const db = Database.Client()
|
||||
db.run(/*sql*/ `DELETE FROM account_state`)
|
||||
db.run(/*sql*/ `DELETE FROM account`)
|
||||
}),
|
||||
)
|
||||
|
||||
const it = testEffect(Layer.merge(AccountRepo.layer, truncate))
|
||||
|
||||
const live = (client: HttpClient.HttpClient) =>
|
||||
Account.layer.pipe(Layer.provide(Layer.succeed(HttpClient.HttpClient, client)))
|
||||
|
||||
const json = (req: Parameters<typeof HttpClientResponse.fromWeb>[0], body: unknown, status = 200) =>
|
||||
HttpClientResponse.fromWeb(
|
||||
req,
|
||||
new Response(JSON.stringify(body), {
|
||||
status,
|
||||
headers: { "content-type": "application/json" },
|
||||
}),
|
||||
)
|
||||
|
||||
const encodeOrg = Schema.encodeSync(Org)
|
||||
|
||||
const org = (id: string, name: string) => encodeOrg(new Org({ id: OrgID.make(id), name }))
|
||||
|
||||
const login = () =>
|
||||
new Login({
|
||||
code: DeviceCode.make("device-code"),
|
||||
user: UserCode.make("user-code"),
|
||||
url: "https://one.example.com/verify",
|
||||
server: "https://one.example.com",
|
||||
expiry: Duration.seconds(600),
|
||||
interval: Duration.seconds(5),
|
||||
})
|
||||
|
||||
const deviceTokenClient = (body: unknown, status = 400) =>
|
||||
HttpClient.make((req) =>
|
||||
Effect.succeed(
|
||||
req.url === "https://one.example.com/auth/device/token" ? json(req, body, status) : json(req, {}, 404),
|
||||
),
|
||||
)
|
||||
|
||||
const poll = (body: unknown, status = 400) =>
|
||||
Account.Service.use((s) => s.poll(login())).pipe(Effect.provide(live(deviceTokenClient(body, status))))
|
||||
|
||||
it.effect("orgsByAccount groups orgs per account", () =>
|
||||
Effect.gen(function* () {
|
||||
yield* AccountRepo.use((r) =>
|
||||
r.persistAccount({
|
||||
id: AccountID.make("user-1"),
|
||||
email: "one@example.com",
|
||||
url: "https://one.example.com",
|
||||
accessToken: AccessToken.make("at_1"),
|
||||
refreshToken: RefreshToken.make("rt_1"),
|
||||
expiry: Date.now() + 60_000,
|
||||
orgID: Option.none(),
|
||||
}),
|
||||
)
|
||||
|
||||
yield* AccountRepo.use((r) =>
|
||||
r.persistAccount({
|
||||
id: AccountID.make("user-2"),
|
||||
email: "two@example.com",
|
||||
url: "https://two.example.com",
|
||||
accessToken: AccessToken.make("at_2"),
|
||||
refreshToken: RefreshToken.make("rt_2"),
|
||||
expiry: Date.now() + 60_000,
|
||||
orgID: Option.none(),
|
||||
}),
|
||||
)
|
||||
|
||||
const seen: Array<string> = []
|
||||
const client = HttpClient.make((req) =>
|
||||
Effect.gen(function* () {
|
||||
seen.push(`${req.method} ${req.url}`)
|
||||
|
||||
if (req.url === "https://one.example.com/api/orgs") {
|
||||
return json(req, [org("org-1", "One")])
|
||||
}
|
||||
|
||||
if (req.url === "https://two.example.com/api/orgs") {
|
||||
return json(req, [org("org-2", "Two A"), org("org-3", "Two B")])
|
||||
}
|
||||
|
||||
return json(req, [], 404)
|
||||
}),
|
||||
)
|
||||
|
||||
const rows = yield* Account.Service.use((s) => s.orgsByAccount()).pipe(Effect.provide(live(client)))
|
||||
|
||||
expect(rows.map((row) => [row.account.id, row.orgs.map((org) => org.id)]).map(([id, orgs]) => [id, orgs])).toEqual([
|
||||
[AccountID.make("user-1"), [OrgID.make("org-1")]],
|
||||
[AccountID.make("user-2"), [OrgID.make("org-2"), OrgID.make("org-3")]],
|
||||
])
|
||||
expect(seen).toEqual(["GET https://one.example.com/api/orgs", "GET https://two.example.com/api/orgs"])
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("token refresh persists the new token", () =>
|
||||
Effect.gen(function* () {
|
||||
const id = AccountID.make("user-1")
|
||||
|
||||
yield* AccountRepo.use((r) =>
|
||||
r.persistAccount({
|
||||
id,
|
||||
email: "user@example.com",
|
||||
url: "https://one.example.com",
|
||||
accessToken: AccessToken.make("at_old"),
|
||||
refreshToken: RefreshToken.make("rt_old"),
|
||||
expiry: Date.now() - 1_000,
|
||||
orgID: Option.none(),
|
||||
}),
|
||||
)
|
||||
|
||||
const client = HttpClient.make((req) =>
|
||||
Effect.succeed(
|
||||
req.url === "https://one.example.com/auth/device/token"
|
||||
? json(req, {
|
||||
access_token: "at_new",
|
||||
refresh_token: "rt_new",
|
||||
expires_in: 60,
|
||||
})
|
||||
: json(req, {}, 404),
|
||||
),
|
||||
)
|
||||
|
||||
const token = yield* Account.Service.use((s) => s.token(id)).pipe(Effect.provide(live(client)))
|
||||
|
||||
expect(Option.getOrThrow(token)).toBeDefined()
|
||||
expect(String(Option.getOrThrow(token))).toBe("at_new")
|
||||
|
||||
const row = yield* AccountRepo.use((r) => r.getRow(id))
|
||||
const value = Option.getOrThrow(row)
|
||||
expect(value.access_token).toBe(AccessToken.make("at_new"))
|
||||
expect(value.refresh_token).toBe(RefreshToken.make("rt_new"))
|
||||
expect(value.token_expiry).toBeGreaterThan(Date.now())
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("config sends the selected org header", () =>
|
||||
Effect.gen(function* () {
|
||||
const id = AccountID.make("user-1")
|
||||
|
||||
yield* AccountRepo.use((r) =>
|
||||
r.persistAccount({
|
||||
id,
|
||||
email: "user@example.com",
|
||||
url: "https://one.example.com",
|
||||
accessToken: AccessToken.make("at_1"),
|
||||
refreshToken: RefreshToken.make("rt_1"),
|
||||
expiry: Date.now() + 60_000,
|
||||
orgID: Option.none(),
|
||||
}),
|
||||
)
|
||||
|
||||
const seen: { auth?: string; org?: string } = {}
|
||||
const client = HttpClient.make((req) =>
|
||||
Effect.gen(function* () {
|
||||
seen.auth = req.headers.authorization
|
||||
seen.org = req.headers["x-org-id"]
|
||||
|
||||
if (req.url === "https://one.example.com/api/config") {
|
||||
return json(req, { config: { theme: "light", seats: 5 } })
|
||||
}
|
||||
|
||||
return json(req, {}, 404)
|
||||
}),
|
||||
)
|
||||
|
||||
const cfg = yield* Account.Service.use((s) => s.config(id, OrgID.make("org-9"))).pipe(Effect.provide(live(client)))
|
||||
|
||||
expect(Option.getOrThrow(cfg)).toEqual({ theme: "light", seats: 5 })
|
||||
expect(seen).toEqual({
|
||||
auth: "Bearer at_1",
|
||||
org: "org-9",
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("poll stores the account and first org on success", () =>
|
||||
Effect.gen(function* () {
|
||||
const client = HttpClient.make((req) =>
|
||||
Effect.succeed(
|
||||
req.url === "https://one.example.com/auth/device/token"
|
||||
? json(req, {
|
||||
access_token: "at_1",
|
||||
refresh_token: "rt_1",
|
||||
token_type: "Bearer",
|
||||
expires_in: 60,
|
||||
})
|
||||
: req.url === "https://one.example.com/api/user"
|
||||
? json(req, { id: "user-1", email: "user@example.com" })
|
||||
: req.url === "https://one.example.com/api/orgs"
|
||||
? json(req, [org("org-1", "One")])
|
||||
: json(req, {}, 404),
|
||||
),
|
||||
)
|
||||
|
||||
const res = yield* Account.Service.use((s) => s.poll(login())).pipe(Effect.provide(live(client)))
|
||||
|
||||
expect(res._tag).toBe("PollSuccess")
|
||||
if (res._tag === "PollSuccess") {
|
||||
expect(res.email).toBe("user@example.com")
|
||||
}
|
||||
|
||||
const active = yield* AccountRepo.use((r) => r.active())
|
||||
expect(Option.getOrThrow(active)).toEqual(
|
||||
expect.objectContaining({
|
||||
id: "user-1",
|
||||
email: "user@example.com",
|
||||
active_org_id: "org-1",
|
||||
}),
|
||||
)
|
||||
}),
|
||||
)
|
||||
|
||||
for (const [name, body, expectedTag] of [
|
||||
[
|
||||
"pending",
|
||||
{
|
||||
error: "authorization_pending",
|
||||
error_description: "The authorization request is still pending",
|
||||
},
|
||||
"PollPending",
|
||||
],
|
||||
[
|
||||
"slow",
|
||||
{
|
||||
error: "slow_down",
|
||||
error_description: "Polling too frequently, please slow down",
|
||||
},
|
||||
"PollSlow",
|
||||
],
|
||||
[
|
||||
"denied",
|
||||
{
|
||||
error: "access_denied",
|
||||
error_description: "The authorization request was denied",
|
||||
},
|
||||
"PollDenied",
|
||||
],
|
||||
[
|
||||
"expired",
|
||||
{
|
||||
error: "expired_token",
|
||||
error_description: "The device code has expired",
|
||||
},
|
||||
"PollExpired",
|
||||
],
|
||||
] as const) {
|
||||
it.effect(`poll returns ${name} for ${body.error}`, () =>
|
||||
Effect.gen(function* () {
|
||||
const result = yield* poll(body)
|
||||
expect(result._tag).toBe(expectedTag)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
it.effect("poll returns poll error for other OAuth errors", () =>
|
||||
Effect.gen(function* () {
|
||||
const result = yield* poll({
|
||||
error: "server_error",
|
||||
error_description: "An unexpected error occurred",
|
||||
})
|
||||
|
||||
expect(result._tag).toBe("PollError")
|
||||
if (result._tag === "PollError") {
|
||||
expect(String(result.cause)).toContain("server_error")
|
||||
}
|
||||
}),
|
||||
)
|
||||
Reference in New Issue
Block a user