Skip to content
Open
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
38 changes: 38 additions & 0 deletions models/bxml/verbs/Refer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { NestableVerb } from '../NestableVerb';
import { SipUri } from './SipUri';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[MINOR] SipUri carries Transfer-specific attributes

The imported SipUri accepts transferAnswerUrl, transferAnswerFallbackUrl, uui, username, password, etc. as optional attributes. Any of those passed here will serialize into the <SipUri> element inside <Refer>, producing malformed BXML — and TypeScript won't catch it.

Python resolved this with a dedicated ReferSipUri class accepting only uri. C# uses a nested Refer.SipUri with only a Uri property. A ReferSipUri type here (or at minimum a narrowed interface) would close that gap:

// models/bxml/verbs/ReferSipUri.ts
export class ReferSipUri extends Verb {
    constructor(uri: string) {
        super('SipUri', uri);
    }
}


export interface ReferAttributes {
referCompleteUrl?: string;
referCompleteMethod?: string;
tag?: string;
}

/**
* @export
* @class Refer
* @extends {NestableVerb}
* Represents a Refer BXML verb.
* NOTE: On success the call is terminated — the remote SIP endpoint redirects away from Bandwidth.
* Recovery BXML in referCompleteUrl only makes sense for failure handling.
*/
export class Refer extends NestableVerb {
attributes: ReferAttributes;

/**
* Creates an instance of Refer
* @param {ReferAttributes} attributes The attributes to add to the element
* @param {SipUri} sipUri The SipUri child element (required for a valid Refer)
*/
constructor(attributes?: ReferAttributes, sipUri?: SipUri) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[MINOR] sipUri is optional but required by the spec

The spec mandates exactly one <SipUri> child. Making it optional here means TypeScript happily accepts new Refer({}) at compile time and produces invalid BXML at runtime.

Python makes sip_uri a required positional argument; C# throws at the builder call. Suggest flipping the signature to make sipUri required and swapping the parameter order to match Transfer's child-last convention:

constructor(sipUri: SipUri, attributes?: ReferAttributes) {
    super('Refer', undefined, attributes, [sipUri]);
}

If keeping optional for API flexibility, add a runtime guard:

if (!sipUri) throw new Error('Refer requires a SipUri child element');

super('Refer', undefined, attributes, sipUri ? [sipUri] : undefined);
}

/**
* Set the SipUri for this Refer verb
* @param {SipUri} sipUri The SipUri to refer to
*/
setSipUri(sipUri: SipUri): void {
this.nestedVerbs = [sipUri];

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[MINOR] setSipUri replaces the entire nestedVerbs array

This works correctly today since <Refer> has exactly one child, but it silently drops any other nested verbs rather than updating a specific slot. Transfer's addTransferRecipients appends; this replaces. Worth a one-line comment making the intent explicit:

// Replaces the single required SipUri child — <Refer> allows exactly one.
this.nestedVerbs = [sipUri];

}
}

1 change: 1 addition & 0 deletions models/bxml/verbs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export * from './PhoneNumber';
export * from './PlayAudio';
export * from './Record';
export * from './Redirect';
export * from './Refer';
export * from './ResumeRecording';
export * from './Ring';
export * from './SendDtmf';
Expand Down
43 changes: 43 additions & 0 deletions tests/unit/models/bxml/verbs/Refer.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Refer, ReferAttributes } from '../../../../../models/bxml/verbs/Refer';
import { SipUri } from '../../../../../models/bxml/verbs/SipUri';

describe('Refer', () => {
test('should generate Refer XML with SipUri and all attributes', () => {
const attributes: ReferAttributes = {
referCompleteUrl: 'https://example.com/handleRefer',
referCompleteMethod: 'POST',
tag: 'my-tag',
};
const sipUri = new SipUri('sip:alice@atlanta.example.com');
const refer = new Refer(attributes, sipUri);

const xml = refer.toBxml();
expect(xml).toContain('<Refer');
expect(xml).toContain('referCompleteUrl="https://example.com/handleRefer"');
expect(xml).toContain('referCompleteMethod="POST"');
expect(xml).toContain('tag="my-tag"');
expect(xml).toContain('<SipUri>sip:alice@atlanta.example.com</SipUri>');
expect(xml).toContain('</Refer>');
});

test('should generate Refer XML with no attributes', () => {
const sipUri = new SipUri('sip:bob@biloxi.example.com');
const refer = new Refer(undefined, sipUri);

const xml = refer.toBxml();
expect(xml).toContain('<Refer>');
expect(xml).toContain('<SipUri>sip:bob@biloxi.example.com</SipUri>');
});

test('setSipUri should replace the nested SipUri', () => {
const sipUri1 = new SipUri('sip:alice@atlanta.example.com');
const sipUri2 = new SipUri('sip:bob@biloxi.example.com');
const refer = new Refer({}, sipUri1);

refer.setSipUri(sipUri2);
const xml = refer.toBxml();
expect(xml).not.toContain('alice');
expect(xml).toContain('sip:bob@biloxi.example.com');
});
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[MINOR] No test for Refer constructed without a SipUri

Since sipUri is currently optional, new Refer({}).toBxml() is valid TypeScript. A test documenting (and ideally asserting on) that behaviour would make the gap visible — and if sipUri is made required (see constructor comment), this becomes a compile-error test instead:

test('should throw when SipUri is missing', () => {
    expect(() => new Refer({})).toThrow();
});