diff --git a/packages/web-api/src/WebClient.spec.js b/packages/web-api/src/WebClient.spec.js index 3320a6363..60fa8a413 100644 --- a/packages/web-api/src/WebClient.spec.js +++ b/packages/web-api/src/WebClient.spec.js @@ -227,6 +227,57 @@ describe('WebClient', function () { }); }); }); + + const threadTsTestPatterns = [ + { method: 'chat.postEphemeral' }, + { method: 'chat.postMessage' }, + { method: 'chat.scheduleMessage' }, + { method: 'files.upload' }, + ]; + + threadTsTestPatterns.reduce((acc, { method, args }) => { + const threadTs = [{ thread_ts: 1503435956.000247, text: 'text' }] + .map(v => ({ method, args: Object.assign({}, v, args) })) + return acc.concat(threadTs) + }, []).forEach(({ method, args }) => { + it(`should send warning to logs when thread_ts in ${method} arguments is a float`, function () { + const logger = { + debug: sinon.spy(), + info: sinon.spy(), + warn: sinon.spy(), + error: sinon.spy(), + setLevel: sinon.spy(), + setName: sinon.spy(), + }; + const warnClient = new WebClient(token, { logLevel: LogLevel.WARN, logger }); + return warnClient.apiCall(method, args) + .then(() => { + assert.isTrue(logger.warn.callCount === 4); + }); + }); + }); + + threadTsTestPatterns.reduce((acc, { method, args }) => { + const threadTs = [{ thread_ts: '1503435956.000247', text: 'text' }] + .map(v => ({ method, args: Object.assign({}, v, args) })) + return acc.concat(threadTs) + }, []).forEach(({ method, args }) => { + it(`should not send warning to logs when thread_ts in ${method} arguments is a string`, function () { + const logger = { + debug: sinon.spy(), + info: sinon.spy(), + warn: sinon.spy(), + error: sinon.spy(), + setLevel: sinon.spy(), + setName: sinon.spy(), + }; + const warnClient = new WebClient(token, { logLevel: LogLevel.WARN, logger }); + return warnClient.apiCall(method, args) + .then(() => { + assert.isTrue(logger.warn.calledThrice); + }); + }); + }); }); describe('with OAuth scopes in the response headers', function () { diff --git a/packages/web-api/src/WebClient.ts b/packages/web-api/src/WebClient.ts index 45ffaa435..9f5a01dc2 100644 --- a/packages/web-api/src/WebClient.ts +++ b/packages/web-api/src/WebClient.ts @@ -156,6 +156,7 @@ export class WebClient extends Methods { warnDeprecations(method, this.logger); warnIfFallbackIsMissing(method, this.logger, options); + warnIfThreadTsIsNotString(method, this.logger, options); if (typeof options === 'string' || typeof options === 'number' || typeof options === 'boolean') { throw new TypeError(`Expected an options argument but instead received a ${typeof options}`); @@ -619,6 +620,7 @@ function warnDeprecations(method: string, logger: Logger): void { * Log a warning when using chat.postMessage without text argument or attachments with fallback argument * @param method api method being called * @param logger instance of we clients logger + * @param options arguments for the Web API method */ function warnIfFallbackIsMissing(method: string, logger: Logger, options?: WebAPICallOptions): void { const targetMethods = ['chat.postEphemeral', 'chat.postMessage', 'chat.scheduleMessage', 'chat.update']; @@ -645,3 +647,18 @@ function warnIfFallbackIsMissing(method: string, logger: Logger, options?: WebAP } } } + +/** + * Log a warning when thread_ts is not a string + * @param method api method being called + * @param logger instance of web clients logger + * @param options arguments for the Web API method + */ +function warnIfThreadTsIsNotString(method: string, logger: Logger, options?: WebAPICallOptions): void { + const targetMethods = ['chat.postEphemeral', 'chat.postMessage', 'chat.scheduleMessage', 'files.upload']; + const isTargetMethod = targetMethods.includes(method); + + if (isTargetMethod && options?.thread_ts !== undefined && typeof options?.thread_ts !== 'string') { + logger.warn(`The given thread_ts value in the request payload for a ${method} call is a float value. We highly recommend using a string value instead.`); + } +}