Skip to content

Commit

Permalink
[steps] [Issue 2261] Fix multiline text messages (#359)
Browse files Browse the repository at this point in the history
* [build-tools] Fix text messages

Added test to check if the multiline message gets sent properly

See: https://linear.app/expo/issue/ENG-11196/create-custom-build-example-slacking-team-members

* [build-tools] Fix text messages

Replacing all occurrences of `\\n` with `\n` after `JSON.stringify()`

See: https://linear.app/expo/issue/ENG-11196/create-custom-build-example-slacking-team-members

* [build-tools] Fix text messages

Extracted the logic to a separate function and added tests for the case that came up during testing

See: https://linear.app/expo/issue/ENG-11196/create-custom-build-example-slacking-team-members

* [build-tools] Revert change

Removed fixing escape characters from sendSlackMessage.ts to make it more global

See: https://linear.app/expo/issue/ENG-11196/create-custom-build-example-slacking-team-members

* [steps] Globally fix escape characters

Added escape character fix to value getter in BuildStepInput.ts

See: expo/eas-cli#2261

* [steps] Add tests

Added tests for global fix for new line escape characters

See: expo/eas-cli#2261
  • Loading branch information
radoslawkrzemien authored Mar 12, 2024
1 parent add3967 commit b8e6954
Show file tree
Hide file tree
Showing 3 changed files with 169 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,60 @@ describe(createSendSlackMessageFunction, () => {
expect(loggerWarnMock).not.toHaveBeenCalled();
});

it('calls the webhook when using multiline plain text input', async () => {
fetchMock.mockImplementation(() => Promise.resolve({ status: 200, ok: true } as Response));
const buildStep = sendSlackMessage.createBuildStepFromFunctionCall(
createGlobalContextMock({}),
{
callInputs: {
message: 'Line 1\nLine 2\n\nLine 3',
},
env: {
SLACK_HOOK_URL: 'https://slack.hook.url',
},
id: sendSlackMessage.id,
}
);
mockLogger(buildStep.ctx.logger);
await buildStep.executeAsync();
expect(fetchMock).toHaveBeenCalledWith('https://slack.hook.url', {
method: 'POST',
body: '{"text":"Line 1\\nLine 2\\n\\nLine 3"}',
headers: { 'Content-Type': 'application/json' },
});
expect(loggerInfoMock).toHaveBeenCalledTimes(4);
expect(loggerInfoMock).toHaveBeenCalledWith('Sending Slack message');
expect(loggerInfoMock).toHaveBeenCalledWith('Slack message sent successfully');
expect(loggerWarnMock).not.toHaveBeenCalled();
});

it('calls the webhook when using multiline plain text input with doubly escaped new lines', async () => {
fetchMock.mockImplementation(() => Promise.resolve({ status: 200, ok: true } as Response));
const buildStep = sendSlackMessage.createBuildStepFromFunctionCall(
createGlobalContextMock({}),
{
callInputs: {
message: 'Line 1\\nLine 2\\n\\nLine 3',
},
env: {
SLACK_HOOK_URL: 'https://slack.hook.url',
},
id: sendSlackMessage.id,
}
);
mockLogger(buildStep.ctx.logger);
await buildStep.executeAsync();
expect(fetchMock).toHaveBeenCalledWith('https://slack.hook.url', {
method: 'POST',
body: '{"text":"Line 1\\nLine 2\\n\\nLine 3"}',
headers: { 'Content-Type': 'application/json' },
});
expect(loggerInfoMock).toHaveBeenCalledTimes(4);
expect(loggerInfoMock).toHaveBeenCalledWith('Sending Slack message');
expect(loggerInfoMock).toHaveBeenCalledWith('Slack message sent successfully');
expect(loggerWarnMock).not.toHaveBeenCalled();
});

it('calls the webhook when using the payload input', async () => {
fetchMock.mockImplementation(() => Promise.resolve({ status: 200, ok: true } as Response));
const buildStep = sendSlackMessage.createBuildStepFromFunctionCall(
Expand Down
21 changes: 19 additions & 2 deletions packages/steps/src/BuildStepInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,13 +113,14 @@ export class BuildStepInput<

const valueDoesNotRequireInterpolation =
rawValue === undefined || typeof rawValue === 'boolean' || typeof rawValue === 'number';
let returnValue;
if (valueDoesNotRequireInterpolation) {
if (typeof rawValue !== this.allowedValueTypeName && rawValue !== undefined) {
throw new BuildStepRuntimeError(
`Input parameter "${this.id}" for step "${this.stepDisplayName}" must be of type "${this.allowedValueTypeName}".`
);
}
return rawValue as BuildStepInputValueTypeWithRequired<T, R>;
returnValue = rawValue as BuildStepInputValueTypeWithRequired<T, R>;
} else {
// `valueDoesNotRequireInterpolation` checks that `rawValue` is not undefined
// so this will never be true.
Expand All @@ -129,7 +130,23 @@ export class BuildStepInput<
valueInterpolatedWithGlobalContext,
(path) => this.ctx.getStepOutputValue(path) ?? ''
);
return this.parseInputValueToAllowedType(valueInterpolatedWithOutputsAndGlobalContext);
returnValue = this.parseInputValueToAllowedType(valueInterpolatedWithOutputsAndGlobalContext);
}
return this.fixEscapeCharacters(returnValue);
}

private fixEscapeCharacters(
input: BuildStepInputValueTypeWithRequired<T, R>
): BuildStepInputValueTypeWithRequired<T, R> {
if (typeof input === 'string') {
return input.replace(/\\n/g, '\n') as BuildStepInputValueTypeWithRequired<T, R>;
} else if (typeof input === 'object') {
for (const property of Object.keys(input)) {
input[property] = this.fixEscapeCharacters(input[property]);
}
return input;
} else {
return input;
}
}

Expand Down
96 changes: 96 additions & 0 deletions packages/steps/src/__tests__/BuildStepInput-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,42 @@ describe(BuildStepInput, () => {
expect(i.value).toEqual('linux');
});

test('context value string with newline characters', () => {
const ctx = createGlobalContextMock({
staticContextContent: {
foo: {
bar: 'Line 1\nLine 2\n\nLine 3',
},
} as unknown as BuildStaticContext,
});
const i = new BuildStepInput(ctx, {
id: 'foo',
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
defaultValue: '${ eas.foo.bar }',
required: true,
allowedValueTypeName: BuildStepInputValueTypeName.STRING,
});
expect(i.value).toEqual('Line 1\nLine 2\n\nLine 3');
});

test('context value string with doubly escaped newline characters', () => {
const ctx = createGlobalContextMock({
staticContextContent: {
foo: {
bar: 'Line 1\\nLine 2\\n\\nLine 3',
},
} as unknown as BuildStaticContext,
});
const i = new BuildStepInput(ctx, {
id: 'foo',
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
defaultValue: '${ eas.foo.bar }',
required: true,
allowedValueTypeName: BuildStepInputValueTypeName.STRING,
});
expect(i.value).toEqual('Line 1\nLine 2\n\nLine 3');
});

test('context value number', () => {
const ctx = createGlobalContextMock({
staticContextContent: {
Expand Down Expand Up @@ -321,6 +357,66 @@ describe(BuildStepInput, () => {
});
});

test('context values in an object with newline characters', () => {
const ctx = createGlobalContextMock({
staticContextContent: {
context_val_1: 'Line 1\nLine 2\n\nLine 3',
} as unknown as BuildStaticContext,
});
const i = new BuildStepInput(ctx, {
id: 'foo',
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
required: true,
allowedValueTypeName: BuildStepInputValueTypeName.JSON,
});
i.set({
foo: 'foo',
bar: '${ eas.context_val_1 }',
baz: {
bazfoo: 'bazfoo',
bazbaz: ['bazbaz', '${ eas.context_val_1 }'],
},
});
expect(i.value).toEqual({
foo: 'foo',
bar: 'Line 1\nLine 2\n\nLine 3',
baz: {
bazfoo: 'bazfoo',
bazbaz: ['bazbaz', 'Line 1\nLine 2\n\nLine 3'],
},
});
});

test('context values in an object with doubly escaped newline characters', () => {
const ctx = createGlobalContextMock({
staticContextContent: {
context_val_1: 'Line 1\\nLine 2\\n\\nLine 3',
} as unknown as BuildStaticContext,
});
const i = new BuildStepInput(ctx, {
id: 'foo',
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
required: true,
allowedValueTypeName: BuildStepInputValueTypeName.JSON,
});
i.set({
foo: 'foo',
bar: '${ eas.context_val_1 }',
baz: {
bazfoo: 'bazfoo',
bazbaz: ['bazbaz', '${ eas.context_val_1 }'],
},
});
expect(i.value).toEqual({
foo: 'foo',
bar: 'Line 1\nLine 2\n\nLine 3',
baz: {
bazfoo: 'bazfoo',
bazbaz: ['bazbaz', 'Line 1\nLine 2\n\nLine 3'],
},
});
});

test('default value number', () => {
const ctx = createGlobalContextMock();
const i = new BuildStepInput(ctx, {
Expand Down

0 comments on commit b8e6954

Please sign in to comment.