mirror of
https://github.com/andrewyng/openworker.git
synced 2026-09-10 22:20:27 +00:00
Bedrock settings: one auth method at a time
'Connect with' segmented choice (API key / profile / IAM keys) shows only that method's fields; non-selected fields are dropped at build so stale values can't leak.
This commit is contained in:
@@ -1265,6 +1265,8 @@ export interface ProviderField {
|
||||
help: string;
|
||||
placeholder: string;
|
||||
default?: string; // pre-filled editable value (e.g. an OpenAI-compatible vendor's endpoint)
|
||||
choices?: { value: string; label: string }[]; // non-empty → segmented choice, not a text input
|
||||
show_when?: Record<string, string> | null; // render only while these fields hold these values
|
||||
}
|
||||
|
||||
export interface ProviderInfo {
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
// Auth-method segmented choice + show_when field visibility (Bedrock's "Connect with"):
|
||||
// only the selected method's fields render, and clicking a segment switches them.
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
|
||||
import { ProviderForm, type ProviderSetupState } from "./ProviderSetup";
|
||||
import type { ProviderInfo } from "../api";
|
||||
|
||||
vi.mock("../tauri", () => ({ openExternal: vi.fn() }));
|
||||
|
||||
afterEach(cleanup);
|
||||
|
||||
const BEDROCK: ProviderInfo = {
|
||||
name: "bedrock",
|
||||
title: "AWS Bedrock",
|
||||
needs_key: true,
|
||||
configured: false,
|
||||
values: {},
|
||||
suggested_models: [],
|
||||
recommended_model: null,
|
||||
fields: [
|
||||
{ key: "region", label: "AWS region", secret: false, required: true, help: "", placeholder: "us-east-1" },
|
||||
{
|
||||
key: "auth_method",
|
||||
label: "Connect with",
|
||||
secret: false,
|
||||
required: false,
|
||||
help: "",
|
||||
placeholder: "",
|
||||
default: "api_key",
|
||||
choices: [
|
||||
{ value: "api_key", label: "Bedrock API key" },
|
||||
{ value: "profile", label: "AWS profile" },
|
||||
{ value: "iam", label: "IAM keys" },
|
||||
],
|
||||
},
|
||||
{ key: "bedrock_api_key", label: "Bedrock API key", secret: true, required: false, help: "", placeholder: "ABSK…", show_when: { auth_method: "api_key" } },
|
||||
{ key: "aws_profile", label: "AWS profile", secret: false, required: false, help: "", placeholder: "default", show_when: { auth_method: "profile" } },
|
||||
{ key: "aws_secret_access_key", label: "Secret access key", secret: true, required: false, help: "", placeholder: "", show_when: { auth_method: "iam" } },
|
||||
],
|
||||
};
|
||||
|
||||
function makePs(fields: Record<string, string>, setFieldValue = vi.fn()): ProviderSetupState {
|
||||
return {
|
||||
providers: [BEDROCK],
|
||||
ordered: [BEDROCK],
|
||||
refreshProviders: async () => {},
|
||||
sel: "bedrock",
|
||||
info: BEDROCK,
|
||||
fields,
|
||||
setFieldValue,
|
||||
dirty: false,
|
||||
verify: { state: "idle" },
|
||||
showEndpoint: false,
|
||||
setShowEndpoint: () => {},
|
||||
keylessOk: new Set(),
|
||||
credentialed: false,
|
||||
savedState: false,
|
||||
secretFilled: true,
|
||||
openProvider: () => {},
|
||||
backToGallery: () => {},
|
||||
runTestAndSave: async () => true,
|
||||
removeKey: async () => {},
|
||||
cancelBackTimer: () => {},
|
||||
statusFor: () => null,
|
||||
saveField: async () => {},
|
||||
fieldSaved: null,
|
||||
};
|
||||
}
|
||||
|
||||
describe("ProviderForm auth-method choice", () => {
|
||||
it("renders only the selected method's fields", () => {
|
||||
render(<ProviderForm ps={makePs({ auth_method: "api_key" })} tp="t" />);
|
||||
expect(screen.getByTestId("t-field-bedrock_api_key")).toBeTruthy();
|
||||
expect(screen.queryByTestId("t-field-aws_profile")).toBeNull();
|
||||
expect(screen.queryByTestId("t-field-aws_secret_access_key")).toBeNull();
|
||||
expect(screen.getByTestId("t-choice-auth_method-api_key").getAttribute("aria-checked")).toBe("true");
|
||||
});
|
||||
|
||||
it("switching the segment swaps the visible fields", () => {
|
||||
const setFieldValue = vi.fn();
|
||||
const { rerender } = render(
|
||||
<ProviderForm ps={makePs({ auth_method: "api_key" }, setFieldValue)} tp="t" />,
|
||||
);
|
||||
fireEvent.click(screen.getByTestId("t-choice-auth_method-profile"));
|
||||
expect(setFieldValue).toHaveBeenCalledWith("auth_method", "profile");
|
||||
rerender(<ProviderForm ps={makePs({ auth_method: "profile" }, setFieldValue)} tp="t" />);
|
||||
expect(screen.getByTestId("t-field-aws_profile")).toBeTruthy();
|
||||
expect(screen.queryByTestId("t-field-bedrock_api_key")).toBeNull();
|
||||
});
|
||||
|
||||
it("iam segment shows the key-pair fields", () => {
|
||||
render(<ProviderForm ps={makePs({ auth_method: "iam" })} tp="t" />);
|
||||
expect(screen.getByTestId("t-field-aws_secret_access_key")).toBeTruthy();
|
||||
expect(screen.queryByTestId("t-field-bedrock_api_key")).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -341,6 +341,41 @@ export function ProviderForm({
|
||||
// A keyed provider's base_url is an expert option — it renders BELOW the key-help
|
||||
// line as its own advanced section (owner nit 2026-07-19), not inside the loop.
|
||||
if (f.key === "base_url" && keyed) return null;
|
||||
// Conditional fields (Bedrock's per-auth-method inputs) render only while their
|
||||
// controlling field holds the matching value.
|
||||
if (f.show_when && !Object.entries(f.show_when).every(([k, v]) => (ps.fields[k] || "") === v))
|
||||
return null;
|
||||
// Segmented choice (e.g. Bedrock's "Connect with"): a button per option, stored
|
||||
// like any field value. Switching methods requires a fresh Test to save.
|
||||
if (f.choices?.length)
|
||||
return (
|
||||
<div key={f.key}>
|
||||
<label className={label}>{f.label}</label>
|
||||
<div className="flex gap-1.5" role="radiogroup" aria-label={f.label}>
|
||||
{f.choices.map((c) => {
|
||||
const active = (ps.fields[f.key] || "") === c.value;
|
||||
return (
|
||||
<button
|
||||
key={c.value}
|
||||
role="radio"
|
||||
aria-checked={active}
|
||||
className={
|
||||
"px-3 py-1.5 rounded-lg border text-[12.5px] transition-colors " +
|
||||
(active
|
||||
? "border-accent text-ink font-medium"
|
||||
: "border-line text-muted hover:border-lineStrong")
|
||||
}
|
||||
data-testid={`${tp}-choice-${f.key}-${c.value}`}
|
||||
onClick={() => ps.setFieldValue(f.key, c.value)}
|
||||
>
|
||||
{c.label}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{f.help && <p className="text-[11.5px] text-faint mt-1">{f.help}</p>}
|
||||
</div>
|
||||
);
|
||||
// Test lives next to the required secret (the API key) when there is one; cloud
|
||||
// providers whose secrets are all optional (Bedrock, Vertex) test from the first
|
||||
// field instead — their credentials may be ambient (~/.aws, ADC).
|
||||
|
||||
Reference in New Issue
Block a user