Skip to content

[Typescript] DGS-23476: Allow id-pool header to be optional for union-of-pools#452

Merged
Tobi Ogunbi (tobiogunbi) merged 1 commit intomasterfrom
DGS-23476
Feb 20, 2026
Merged

[Typescript] DGS-23476: Allow id-pool header to be optional for union-of-pools#452
Tobi Ogunbi (tobiogunbi) merged 1 commit intomasterfrom
DGS-23476

Conversation

@tobiogunbi
Copy link
Copy Markdown
Member

@tobiogunbi Tobi Ogunbi (tobiogunbi) commented Feb 20, 2026

Please prefix all TypeScript pull-requests with [Typescript]

What

Checklist

  • Contains customer facing changes? Including API/behavior changes
  • Did you add sufficient unit test and/or integration test coverage for this PR?
    • If not, please explain why it is not required

References

JIRA:

Test & Review

Open questions / Follow-ups

Copilot AI review requested due to automatic review settings February 20, 2026 01:02
@tobiogunbi Tobi Ogunbi (tobiogunbi) changed the title DGS-23476: Allow id-pool header to be optional for union-of-pools [Typescript] DGS-23476: Allow id-pool header to be optional for union-of-pools Feb 20, 2026
@tobiogunbi Tobi Ogunbi (tobiogunbi) merged commit c361574 into master Feb 20, 2026
5 checks passed
@tobiogunbi Tobi Ogunbi (tobiogunbi) deleted the DGS-23476 branch February 20, 2026 01:14
@sonarqube-confluent
Copy link
Copy Markdown

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR enhances the bearer authentication functionality for Schema Registry by making the identityPoolId parameter optional and allowing it to accept an array of pool IDs for union-of-pools scenarios. Previously, identityPoolId was required and only accepted a single string value.

Changes:

  • Modified identityPoolId type from string to string | string[] in BearerAuthCredentials interface
  • Made identityPoolId parameter optional (no longer required for bearer auth)
  • Added logic to join array of pool IDs into comma-separated header value
  • Added comprehensive test coverage for single pool, multiple pools (array), and omitted pool scenarios
  • Updated example documentation to clarify comma-separated format

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.

File Description
schemaregistry/rest-service.ts Modified type signature and handling logic for identityPoolId to support optional string or array of strings
schemaregistry/test/rest-service.spec.ts Added three new test cases covering single pool, array of pools, and omitted pool scenarios
schemaregistry-examples/src/constants.ts Updated comment to clarify comma-separated format for union-of-pools

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +131 to +137
const poolId = Array.isArray(bearerAuthCredentials.identityPoolId)
? bearerAuthCredentials.identityPoolId.join(',')
: bearerAuthCredentials.identityPoolId;

if (poolId) {
headers['Confluent-Identity-Pool-Id'] = poolId;
}
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty array case is not handled properly. When identityPoolId is an empty array, the join operation will produce an empty string, which is a falsy value and will prevent the header from being set. However, this behavior may be misleading since an empty array is semantically different from an undefined/omitted value. Consider adding explicit validation to reject empty arrays or handle them distinctly.

Copilot uses AI. Check for mistakes.
Comment on lines +73 to +145
describe('RestService Bearer Auth', () => {
let mock: InstanceType<typeof MockAdapter>;

beforeEach(() => {
mock = new MockAdapter(axios);
});

afterEach(() => {
mock.reset();
});

it('should set Confluent-Identity-Pool-Id header when identityPoolId is provided', async () => {
const bearerAuth: BearerAuthCredentials = {
credentialsSource: 'STATIC_TOKEN',
token: 'my-token',
logicalCluster: 'lsrc-abc123',
identityPoolId: 'pool-Gx30',
};

const restService = new RestService(
['http://localhost'], false, {}, undefined, bearerAuth,
maxRetries, retriesWaitMs, retriesMaxWaitMs
);

mock.onGet('/subjects').reply(200, ['subject1']);
await restService.handleRequest('/subjects', 'GET');

const requestHeaders = mock.history.get[0].headers;
expect(requestHeaders?.['Confluent-Identity-Pool-Id']).toBe('pool-Gx30');
expect(requestHeaders?.['target-sr-cluster']).toBe('lsrc-abc123');
expect(requestHeaders?.['Authorization']).toBe('Bearer my-token');
});

it('should join array of identityPoolIds into comma-separated header', async () => {
const bearerAuth: BearerAuthCredentials = {
credentialsSource: 'STATIC_TOKEN',
token: 'my-token',
logicalCluster: 'lsrc-abc123',
identityPoolId: ['pool-1', 'pool-2', 'pool-3'],
};

const restService = new RestService(
['http://localhost'], false, {}, undefined, bearerAuth,
maxRetries, retriesWaitMs, retriesMaxWaitMs
);

mock.onGet('/subjects').reply(200, ['subject1']);
await restService.handleRequest('/subjects', 'GET');

const requestHeaders = mock.history.get[0].headers;
expect(requestHeaders?.['Confluent-Identity-Pool-Id']).toBe('pool-1,pool-2,pool-3');
});

it('should not set Confluent-Identity-Pool-Id header when identityPoolId is omitted', async () => {
const bearerAuth: BearerAuthCredentials = {
credentialsSource: 'STATIC_TOKEN',
token: 'my-token',
logicalCluster: 'lsrc-abc123',
};

const restService = new RestService(
['http://localhost'], false, {}, undefined, bearerAuth,
maxRetries, retriesWaitMs, retriesMaxWaitMs
);

mock.onGet('/subjects').reply(200, ['subject1']);
await restService.handleRequest('/subjects', 'GET');

const requestHeaders = mock.history.get[0].headers;
expect(requestHeaders?.['Confluent-Identity-Pool-Id']).toBeUndefined();
expect(requestHeaders?.['target-sr-cluster']).toBe('lsrc-abc123');
});
});
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing test coverage for edge case where identityPoolId is an empty array. This should either be tested to ensure it behaves correctly (header not set) or validation should be added to reject empty arrays with a clear error message.

Copilot uses AI. Check for mistakes.
const oauthClientSecret = '<your-client-secret>';
const scope = '<your-scope>'; // e.g. 'schemaregistry';
const identityPoolId = '<your-pool>'; // e.g. pool-Gx30
const identityPoolId = '<your-pool>'; // single pool e.g. 'pool-Gx30', or comma-separated for union-of-pools e.g. 'pool-Gx30,pool-Ab12'
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment suggests using comma-separated string for union-of-pools (e.g., 'pool-Gx30,pool-Ab12'), but the code now accepts an array of strings (e.g., ['pool-Gx30', 'pool-Ab12']). The comment should mention both options: passing a single string, an array of strings for union-of-pools, or a comma-separated string. Update the comment to clarify that arrays are the preferred approach.

Suggested change
const identityPoolId = '<your-pool>'; // single pool e.g. 'pool-Gx30', or comma-separated for union-of-pools e.g. 'pool-Gx30,pool-Ab12'
const identityPoolId = '<your-pool>'; // single pool e.g. 'pool-Gx30'; for union-of-pools prefer an array of strings e.g. ['pool-Gx30', 'pool-Ab12'], or use a comma-separated string e.g. 'pool-Gx30,pool-Ab12'

Copilot uses AI. Check for mistakes.
retriesMaxWaitMs?: number,
}


Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary blank line added. This appears to be accidental whitespace that should be removed for consistency with the rest of the file.

Suggested change

Copilot uses AI. Check for mistakes.
Comment on lines +132 to +133
? bearerAuthCredentials.identityPoolId.join(',')
: bearerAuthCredentials.identityPoolId;
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing validation for array elements. If the identityPoolId array contains empty strings, null, or undefined values, these will be joined into the header value (e.g., ['pool-1', '', 'pool-3'] becomes 'pool-1,,pool-3'). Consider filtering out falsy values or validating that all array elements are non-empty strings before joining.

Suggested change
? bearerAuthCredentials.identityPoolId.join(',')
: bearerAuthCredentials.identityPoolId;
? bearerAuthCredentials.identityPoolId
.filter((id): id is string => typeof id === 'string' && id.trim().length > 0)
.join(',')
: (typeof bearerAuthCredentials.identityPoolId === 'string'
&& bearerAuthCredentials.identityPoolId.trim().length > 0
? bearerAuthCredentials.identityPoolId
: undefined);

Copilot uses AI. Check for mistakes.
const requestHeaders = mock.history.get[0].headers;
expect(requestHeaders?.['Confluent-Identity-Pool-Id']).toBeUndefined();
expect(requestHeaders?.['target-sr-cluster']).toBe('lsrc-abc123');
});
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing test coverage for edge cases with invalid array elements such as empty strings, whitespace-only strings, or arrays containing null/undefined values. Tests should verify that the implementation either handles these gracefully or rejects them with clear error messages.

Suggested change
});
});
it('should handle identityPoolId array containing empty strings', async () => {
const bearerAuth: BearerAuthCredentials = {
credentialsSource: 'STATIC_TOKEN',
token: 'my-token',
logicalCluster: 'lsrc-abc123',
identityPoolId: ['pool-1', '', 'pool-3'],
};
const restService = new RestService(
['http://localhost'], false, {}, undefined, bearerAuth,
maxRetries, retriesWaitMs, retriesMaxWaitMs
);
mock.onGet('/subjects').reply(200, ['subject1']);
await restService.handleRequest('/subjects', 'GET');
const requestHeaders = mock.history.get[0].headers;
// Document current behavior: empty string elements result in consecutive commas.
expect(requestHeaders?.['Confluent-Identity-Pool-Id']).toBe('pool-1,,pool-3');
});
it('should handle identityPoolId array containing whitespace-only strings', async () => {
const bearerAuth: BearerAuthCredentials = {
credentialsSource: 'STATIC_TOKEN',
token: 'my-token',
logicalCluster: 'lsrc-abc123',
identityPoolId: ['pool-1', ' ', 'pool-3'],
};
const restService = new RestService(
['http://localhost'], false, {}, undefined, bearerAuth,
maxRetries, retriesWaitMs, retriesMaxWaitMs
);
mock.onGet('/subjects').reply(200, ['subject1']);
await restService.handleRequest('/subjects', 'GET');
const requestHeaders = mock.history.get[0].headers;
// Document current behavior: whitespace-only elements are preserved as-is in the joined string.
expect(requestHeaders?.['Confluent-Identity-Pool-Id']).toBe('pool-1, ,pool-3');
});
it('should handle identityPoolId array containing null values', async () => {
const bearerAuth: BearerAuthCredentials = {
credentialsSource: 'STATIC_TOKEN',
token: 'my-token',
logicalCluster: 'lsrc-abc123',
identityPoolId: ['pool-1', null as unknown as string, 'pool-3'],
};
const restService = new RestService(
['http://localhost'], false, {}, undefined, bearerAuth,
maxRetries, retriesWaitMs, retriesMaxWaitMs
);
mock.onGet('/subjects').reply(200, ['subject1']);
await restService.handleRequest('/subjects', 'GET');
const requestHeaders = mock.history.get[0].headers;
// Document current behavior: null elements are treated as empty strings when joined.
expect(requestHeaders?.['Confluent-Identity-Pool-Id']).toBe('pool-1,,pool-3');
});
it('should handle identityPoolId array containing undefined values', async () => {
const bearerAuth: BearerAuthCredentials = {
credentialsSource: 'STATIC_TOKEN',
token: 'my-token',
logicalCluster: 'lsrc-abc123',
identityPoolId: ['pool-1', undefined as unknown as string, 'pool-3'],
};
const restService = new RestService(
['http://localhost'], false, {}, undefined, bearerAuth,
maxRetries, retriesWaitMs, retriesMaxWaitMs
);
mock.onGet('/subjects').reply(200, ['subject1']);
await restService.handleRequest('/subjects', 'GET');
const requestHeaders = mock.history.get[0].headers;
// Document current behavior: undefined elements are treated as empty strings when joined.
expect(requestHeaders?.['Confluent-Identity-Pool-Id']).toBe('pool-1,,pool-3');
});

Copilot uses AI. Check for mistakes.
Copilot AI mentioned this pull request Mar 2, 2026
2 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants