Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions tests/unit/web/pm-wizard-state.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
isStep2Complete,
isStep3Complete,
isStep4Complete,
shouldUseStoredCredentials,
wizardReducer,
} from '../../../web/src/components/projects/pm-wizard-state.js';

Expand Down Expand Up @@ -619,6 +620,104 @@ describe('areCredentialsReady', () => {
});
});

describe('shouldUseStoredCredentials', () => {
// When editing an existing integration, the form does NOT pre-fill
// the API key for security — `hasStoredCredentials` is flipped true
// but e.g. `linearApiKey` is empty. Wizard mutations (verify,
// createLabel, createCustomField) detect this and pass `projectId`
// to the backend so it resolves the stored credentials.
//
// Fresh setup (not editing) → always use form-state credentials.
// Edit mode where the user re-typed a key → use the fresh key.
// Edit mode with stored creds + empty key → use projectId.

it('linear: false in fresh-setup mode (no editing)', () => {
const state = { ...createInitialState(), provider: 'linear' as const };
expect(shouldUseStoredCredentials(state)).toBe(false);
});

it('linear: true in edit mode with stored creds and empty apiKey', () => {
const state: WizardState = {
...createInitialState(),
provider: 'linear' as const,
isEditing: true,
hasStoredCredentials: true,
linearApiKey: '',
};
expect(shouldUseStoredCredentials(state)).toBe(true);
});

it('linear: false in edit mode when user re-typed the apiKey', () => {
const state: WizardState = {
...createInitialState(),
provider: 'linear' as const,
isEditing: true,
hasStoredCredentials: true,
linearApiKey: 'lin_fresh_typed_key',
};
expect(shouldUseStoredCredentials(state)).toBe(false);
});

it('trello: true in edit mode with stored creds and empty apiKey', () => {
const state: WizardState = {
...createInitialState(),
provider: 'trello' as const,
isEditing: true,
hasStoredCredentials: true,
trelloApiKey: '',
trelloToken: '',
};
expect(shouldUseStoredCredentials(state)).toBe(true);
});

it('trello: false in edit mode when user re-typed the apiKey', () => {
const state: WizardState = {
...createInitialState(),
provider: 'trello' as const,
isEditing: true,
hasStoredCredentials: true,
trelloApiKey: 'fresh_key',
trelloToken: '',
};
expect(shouldUseStoredCredentials(state)).toBe(false);
});

it('jira: true in edit mode with stored creds and empty apiToken', () => {
const state: WizardState = {
...createInitialState(),
provider: 'jira' as const,
isEditing: true,
hasStoredCredentials: true,
jiraEmail: '',
jiraApiToken: '',
};
expect(shouldUseStoredCredentials(state)).toBe(true);
});

it('jira: false in edit mode when user re-typed the apiToken', () => {
const state: WizardState = {
...createInitialState(),
provider: 'jira' as const,
isEditing: true,
hasStoredCredentials: true,
jiraEmail: '',
jiraApiToken: 'fresh_token',
};
expect(shouldUseStoredCredentials(state)).toBe(false);
});

it('false when edit mode but hasStoredCredentials is false (user deleted creds)', () => {
const state: WizardState = {
...createInitialState(),
provider: 'linear' as const,
isEditing: true,
hasStoredCredentials: false,
linearApiKey: '',
};
expect(shouldUseStoredCredentials(state)).toBe(false);
});
});

// ============================================================================
// buildEditState
// ============================================================================
Expand Down
2 changes: 1 addition & 1 deletion web/src/components/projects/pm-providers/jira/wizard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ export const jiraProviderWizard: ProviderWizardDefinition = {

useProviderHooks: ({ state, dispatch, projectId, advanceToStep }) => {
const discovery = useJiraDiscovery(state, dispatch, advanceToStep, projectId ?? '');
const customField = useJiraCustomFieldCreation(state, dispatch);
const customField = useJiraCustomFieldCreation(state, dispatch, projectId ?? '');
const queryClient = useQueryClient();

const onCreateCustomField = (_slotKey: string, name: string) => {
Expand Down
2 changes: 1 addition & 1 deletion web/src/components/projects/pm-providers/linear/wizard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ export const linearProviderWizard: ProviderWizardDefinition = {

useProviderHooks: ({ state, dispatch, projectId, advanceToStep }) => {
const discovery = useLinearDiscovery(state, dispatch, advanceToStep, projectId ?? '');
const labels = useLinearLabelCreation(state, dispatch);
const labels = useLinearLabelCreation(state, dispatch, projectId ?? '');
// Lift the LINEAR_WEBHOOK_SECRET credential lookup from the parent
// wizard (`pm-wizard.tsx`) into the provider hooks so the Linear
// webhook step adapter can compose the shared `WebhookUrlDisplayStep`
Expand Down
4 changes: 2 additions & 2 deletions web/src/components/projects/pm-providers/trello/wizard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,8 +274,8 @@ export const trelloProviderWizard: ProviderWizardDefinition = {

useProviderHooks: ({ state, dispatch, projectId, advanceToStep }) => {
const discovery = useTrelloDiscovery(state, dispatch, advanceToStep, projectId ?? '');
const labels = useTrelloLabelCreation(state, dispatch);
const customField = useTrelloCustomFieldCreation(state, dispatch);
const labels = useTrelloLabelCreation(state, dispatch, projectId ?? '');
const customField = useTrelloCustomFieldCreation(state, dispatch, projectId ?? '');
const queryClient = useQueryClient();

const [creatingSlot, setCreatingSlot] = useState<string | null>(null);
Expand Down
Loading
Loading