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
50 changes: 26 additions & 24 deletions src/commands/broadcasts/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,26 @@ Scheduling:
);
}

if (opts.html !== undefined && opts.htmlFile !== undefined) {
outputError(
{
message: '--html and --html-file are mutually exclusive.',
code: 'invalid_options',
},
{ json: globalOpts.json },
);
}

if (opts.text !== undefined && opts.textFile !== undefined) {
outputError(
{
message: '--text and --text-file are mutually exclusive.',
code: 'invalid_options',
},
{ json: globalOpts.json },
);
}

let from = opts.from;
let subject = opts.subject;
let segmentId = opts.segmentId;
Expand Down Expand Up @@ -186,30 +206,12 @@ Scheduling:
}
}

let html = opts.html;
let text = opts.text;

if (opts.htmlFile) {
if (opts.html) {
process.stderr.write(
'Warning: both --html and --html-file provided; using --html-file\n',
);
}
html = readFile(opts.htmlFile, globalOpts);
}

if (opts.textFile) {
if (opts.text) {
process.stderr.write(
'Warning: both --text and --text-file provided; using --text-file\n',
);
}
text = readFile(opts.textFile, globalOpts);
}

if (opts.reactEmail) {
html = await buildReactEmailHtml(opts.reactEmail, globalOpts);
}
const html = opts.reactEmail
? await buildReactEmailHtml(opts.reactEmail, globalOpts)
: opts.htmlFile
? readFile(opts.htmlFile, globalOpts)
: opts.html;
let text = opts.textFile ? readFile(opts.textFile, globalOpts) : opts.text;

if (!html && !text) {
if (!isInteractive() || globalOpts.json) {
Expand Down
48 changes: 26 additions & 22 deletions src/commands/broadcasts/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,32 +108,36 @@ Variable interpolation:
);
}

const id = await pickId(idArg, broadcastPickerConfig, globalOpts);

let html = opts.html;
let text = opts.text;

if (opts.htmlFile != null) {
if (opts.html != null) {
process.stderr.write(
'Warning: both --html and --html-file provided; using --html-file\n',
);
}
html = readFile(opts.htmlFile, globalOpts);
if (opts.html != null && opts.htmlFile != null) {
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
outputError(
{
message: '--html and --html-file are mutually exclusive.',
code: 'invalid_options',
},
{ json: globalOpts.json },
);
}

if (opts.textFile != null) {
if (opts.text != null) {
process.stderr.write(
'Warning: both --text and --text-file provided; using --text-file\n',
);
}
text = readFile(opts.textFile, globalOpts);
if (opts.text != null && opts.textFile != null) {
outputError(
{
message: '--text and --text-file are mutually exclusive.',
code: 'invalid_options',
},
{ json: globalOpts.json },
);
}

if (opts.reactEmail != null) {
html = await buildReactEmailHtml(opts.reactEmail, globalOpts);
}
const id = await pickId(idArg, broadcastPickerConfig, globalOpts);

const html =
opts.reactEmail != null
? await buildReactEmailHtml(opts.reactEmail, globalOpts)
: opts.htmlFile != null
? readFile(opts.htmlFile, globalOpts)
: opts.html;
const text =
opts.textFile != null ? readFile(opts.textFile, globalOpts) : opts.text;

await runWrite(
{
Expand Down
92 changes: 48 additions & 44 deletions tests/commands/broadcasts/create.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -548,64 +548,68 @@ describe('broadcasts create command', () => {
expect(args.text).toBe('Plain text from file');
});

test('warns to stderr when --html and --html-file both provided, html-file wins', async () => {
spies = setupOutputSpies();
readFileSpy = vi
.spyOn(files, 'readFile')
.mockReturnValue('<p>From file</p>');
test('errors with invalid_options when --html and --html-file both provided', async () => {
setNonInteractive();
errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
exitSpy = mockExitThrow();

const { createBroadcastCommand } = await import(
'../../../src/commands/broadcasts/create'
);
await createBroadcastCommand.parseAsync(
[
'--from',
'hello@domain.com',
'--subject',
'News',
'--segment-id',
'7b1e0a3d-4c5f-4e8a-9b2d-1a3c5e7f9b2d',
'--html',
'<p>Inline</p>',
'--html-file',
'/fake/email.html',
],
{ from: 'user' },
await expectExit1(() =>
createBroadcastCommand.parseAsync(
[
'--from',
'hello@domain.com',
'--subject',
'News',
'--segment-id',
'7b1e0a3d-4c5f-4e8a-9b2d-1a3c5e7f9b2d',
'--html',
'<p>Inline</p>',
'--html-file',
'/fake/email.html',
],
{ from: 'user' },
),
);

const stderrOutput = spies.stderrSpy.mock.calls.map((c) => c[0]).join('');
expect(stderrOutput).toContain('--html-file');
const args = mockCreate.mock.calls[0][0] as Record<string, unknown>;
expect(args.html).toBe('<p>From file</p>');
const output = errorSpy.mock.calls.map((c) => c[0]).join(' ');
expect(output).toContain('invalid_options');
expect(output).toContain('mutually exclusive');
expect(mockCreate).not.toHaveBeenCalled();
});

test('warns to stderr when --text and --text-file both provided, text-file wins', async () => {
spies = setupOutputSpies();
readFileSpy = vi.spyOn(files, 'readFile').mockReturnValue('From file');
test('errors with invalid_options when --text and --text-file both provided', async () => {
setNonInteractive();
errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
exitSpy = mockExitThrow();

const { createBroadcastCommand } = await import(
'../../../src/commands/broadcasts/create'
);
await createBroadcastCommand.parseAsync(
[
'--from',
'hello@domain.com',
'--subject',
'News',
'--segment-id',
'7b1e0a3d-4c5f-4e8a-9b2d-1a3c5e7f9b2d',
'--text',
'Inline text',
'--text-file',
'/fake/body.txt',
],
{ from: 'user' },
await expectExit1(() =>
createBroadcastCommand.parseAsync(
[
'--from',
'hello@domain.com',
'--subject',
'News',
'--segment-id',
'7b1e0a3d-4c5f-4e8a-9b2d-1a3c5e7f9b2d',
'--text',
'Inline text',
'--text-file',
'/fake/body.txt',
],
{ from: 'user' },
),
);

const stderrOutput = spies.stderrSpy.mock.calls.map((c) => c[0]).join('');
expect(stderrOutput).toContain('--text-file');
const args = mockCreate.mock.calls[0][0] as Record<string, unknown>;
expect(args.text).toBe('From file');
const output = errorSpy.mock.calls.map((c) => c[0]).join(' ');
expect(output).toContain('invalid_options');
expect(output).toContain('mutually exclusive');
expect(mockCreate).not.toHaveBeenCalled();
});

test('errors with invalid_options when --html-file - and --text-file - both read stdin', async () => {
Expand Down
72 changes: 38 additions & 34 deletions tests/commands/broadcasts/update.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -341,54 +341,58 @@ describe('broadcasts update command', () => {
expect(payload.text).toBe('Text from empty path');
});

test('warns to stderr when --html and --html-file both provided, html-file wins', async () => {
spies = setupOutputSpies();
readFileSpy = vi
.spyOn(files, 'readFile')
.mockReturnValue('<p>From file</p>');
test('errors with invalid_options when --html and --html-file both provided', async () => {
setNonInteractive();
errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
exitSpy = mockExitThrow();

const { updateBroadcastCommand } = await import(
'../../../src/commands/broadcasts/update'
);
await updateBroadcastCommand.parseAsync(
[
'd1c2b3a4-5e6f-7a8b-9c0d-e1f2a3b4c5d6',
'--html',
'<p>Inline</p>',
'--html-file',
'/fake/email.html',
],
{ from: 'user' },
await expectExit1(() =>
updateBroadcastCommand.parseAsync(
[
'd1c2b3a4-5e6f-7a8b-9c0d-e1f2a3b4c5d6',
'--html',
'<p>Inline</p>',
'--html-file',
'/fake/email.html',
],
{ from: 'user' },
),
);

const stderrOutput = spies.stderrSpy.mock.calls.map((c) => c[0]).join('');
expect(stderrOutput).toContain('--html-file');
const payload = mockUpdate.mock.calls[0][1] as Record<string, unknown>;
expect(payload.html).toBe('<p>From file</p>');
const output = errorSpy.mock.calls.map((c) => c[0]).join(' ');
expect(output).toContain('invalid_options');
expect(output).toContain('mutually exclusive');
expect(mockUpdate).not.toHaveBeenCalled();
});

test('warns to stderr when --text and --text-file both provided, text-file wins', async () => {
spies = setupOutputSpies();
readFileSpy = vi.spyOn(files, 'readFile').mockReturnValue('From file');
test('errors with invalid_options when --text and --text-file both provided', async () => {
setNonInteractive();
errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
exitSpy = mockExitThrow();

const { updateBroadcastCommand } = await import(
'../../../src/commands/broadcasts/update'
);
await updateBroadcastCommand.parseAsync(
[
'd1c2b3a4-5e6f-7a8b-9c0d-e1f2a3b4c5d6',
'--text',
'Inline text',
'--text-file',
'/fake/body.txt',
],
{ from: 'user' },
await expectExit1(() =>
updateBroadcastCommand.parseAsync(
[
'd1c2b3a4-5e6f-7a8b-9c0d-e1f2a3b4c5d6',
'--text',
'Inline text',
'--text-file',
'/fake/body.txt',
],
{ from: 'user' },
),
);

const stderrOutput = spies.stderrSpy.mock.calls.map((c) => c[0]).join('');
expect(stderrOutput).toContain('--text-file');
const payload = mockUpdate.mock.calls[0][1] as Record<string, unknown>;
expect(payload.text).toBe('From file');
const output = errorSpy.mock.calls.map((c) => c[0]).join(' ');
expect(output).toContain('invalid_options');
expect(output).toContain('mutually exclusive');
expect(mockUpdate).not.toHaveBeenCalled();
});

test('errors with invalid_options when --html-file - and --text-file - both read stdin', async () => {
Expand Down