diff --git a/keyserver/src/responders/message-responders.js b/keyserver/src/responders/message-responders.js --- a/keyserver/src/responders/message-responders.js +++ b/keyserver/src/responders/message-responders.js @@ -1,7 +1,7 @@ // @flow import invariant from 'invariant'; -import t from 'tcomb'; +import t, { type TInterface } from 'tcomb'; import { onlyOneEmojiRegex } from 'lib/shared/emojis.js'; import { @@ -23,11 +23,14 @@ type SendEditMessageResponse, type FetchPinnedMessagesRequest, type FetchPinnedMessagesResult, + messageTruncationStatusesValidator, + rawMessageInfoValidator, } from 'lib/types/message-types.js'; import type { EditMessageData } from 'lib/types/messages/edit.js'; import type { ReactionMessageData } from 'lib/types/messages/reaction.js'; import type { TextMessageData } from 'lib/types/messages/text.js'; import { threadPermissions } from 'lib/types/thread-types.js'; +import { userInfosValidator } from 'lib/types/user-types.js'; import { ServerError } from 'lib/utils/errors.js'; import { values } from 'lib/utils/objects.js'; import { @@ -65,6 +68,10 @@ text: t.String, sidebarCreation: t.maybe(t.Boolean), }); + +export const sendMessageResponseValidator: TInterface = + tShape({ newMessageInfo: rawMessageInfoValidator }); + async function textMessageCreationResponder( viewer: Viewer, input: any, @@ -118,6 +125,14 @@ cursors: t.dict(t.String, t.maybe(t.String)), numberPerThread: t.maybe(t.Number), }); + +export const fetchMessageInfosResponseValidator: TInterface = + tShape({ + rawMessageInfos: t.list(rawMessageInfoValidator), + truncationStatuses: messageTruncationStatusesValidator, + userInfos: userInfosValidator, + }); + async function messageFetchResponder( viewer: Viewer, input: any, @@ -305,6 +320,12 @@ targetMessageID: t.String, text: t.String, }); + +export const sendEditMessageResponseValidator: TInterface = + tShape({ + newMessageInfos: t.list(rawMessageInfoValidator), + }); + async function editMessageCreationResponder( viewer: Viewer, input: any, @@ -388,6 +409,12 @@ const fetchPinnedMessagesResponderInputValidator = tShape({ threadID: t.String, }); + +export const fetchPinnedMessagesResultValidator: TInterface = + tShape({ + pinnedMessages: t.list(rawMessageInfoValidator), + }); + async function fetchPinnedMessagesResponder( viewer: Viewer, input: any, diff --git a/keyserver/src/responders/report-responders.js b/keyserver/src/responders/report-responders.js --- a/keyserver/src/responders/report-responders.js +++ b/keyserver/src/responders/report-responders.js @@ -2,7 +2,7 @@ import type { $Response, $Request } from 'express'; import t from 'tcomb'; -import type { TStructProps } from 'tcomb'; +import type { TInterface, TStructProps } from 'tcomb'; import { type ReportCreationResponse, @@ -12,7 +12,9 @@ type ThreadInconsistencyReportShape, type EntryInconsistencyReportShape, reportTypes, + reportInfoValidator, } from 'lib/types/report-types.js'; +import { userInfoValidator } from 'lib/types/user-types.js'; import { ServerError } from 'lib/utils/errors.js'; import { tShape, @@ -131,6 +133,9 @@ userInconsistencyReportCreationRequest, ]); +export const reportCreationResponseValidator: TInterface = + tShape({ id: t.String }); + async function reportCreationResponder( viewer: Viewer, input: any, @@ -203,6 +208,12 @@ cursor: t.maybe(t.String), }); +export const fetchErrorReportInfosResponseValidator: TInterface = + tShape({ + reports: t.list(reportInfoValidator), + userInfos: t.list(userInfoValidator), + }); + async function errorReportFetchInfosResponder( viewer: Viewer, input: any, diff --git a/keyserver/src/responders/responder-validators.test.js b/keyserver/src/responders/responder-validators.test.js --- a/keyserver/src/responders/responder-validators.test.js +++ b/keyserver/src/responders/responder-validators.test.js @@ -15,9 +15,24 @@ } from './entry-responders.js'; import { getSessionPublicKeysResponseValidator } from './keys-responders.js'; import { messageReportCreationResultValidator } from './message-report-responder.js'; +import { + fetchMessageInfosResponseValidator, + fetchPinnedMessagesResultValidator, + sendEditMessageResponseValidator, + sendMessageResponseValidator, +} from './message-responders.js'; import { relationshipErrorsValidator } from './relationship-responders.js'; +import { reportCreationResponseValidator } from './report-responders.js'; import { userSearchResultValidator } from './search-responders.js'; import { siweNonceResponseValidator } from './siwe-nonce-responders.js'; +import { + changeThreadSettingsResultValidator, + leaveThreadResultValidator, + newThreadResponseValidator, + threadFetchMediaResultValidator, + threadJoinResultValidator, + toggleMessagePinResultValidator, +} from './thread-responders.js'; import { logInResponseValidator, registerResponseValidator, @@ -617,3 +632,289 @@ ).toBe(false); }); }); + +describe('thread responders', () => { + it('should validate change thread settings response', () => { + const response = { + updatesResult: { + newUpdates: [ + { + type: 1, + id: '93601', + time: 1682759546258, + threadInfo: { + id: '92796', + type: 6, + name: '', + description: '', + color: 'b8753d', + creationTime: 1682076700918, + parentThreadID: '1', + members: [], + roles: {}, + currentUser: { + role: '85172', + permissions: {}, + subscription: { + home: true, + pushNotifs: true, + }, + unread: false, + }, + repliesCount: 0, + containingThreadID: '1', + community: '1', + pinnedCount: 0, + }, + }, + ], + }, + newMessageInfos: [ + { + type: 4, + threadID: '92796', + creatorID: '83928', + time: 1682759546275, + field: 'color', + value: 'b8753d', + id: '93602', + }, + ], + }; + expect(changeThreadSettingsResultValidator.is(response)).toBe(true); + expect( + changeThreadSettingsResultValidator.is({ + ...response, + newMessageInfos: undefined, + }), + ).toBe(false); + }); + + it('should validate leave thread response', () => { + const response = { + updatesResult: { + newUpdates: [ + { type: 3, id: '93595', time: 1682759498811, threadID: '93561' }, + ], + }, + }; + expect(leaveThreadResultValidator.is(response)).toBe(true); + expect( + leaveThreadResultValidator.is({ + ...response, + updatedResult: undefined, + }), + ).toBe(false); + }); + + it('should validate new thread response', () => { + const response = { + newThreadID: '93619', + updatesResult: { + newUpdates: [ + { + type: 4, + id: '93621', + time: 1682759805331, + threadInfo: { + id: '93619', + type: 5, + name: 'a', + description: '', + color: 'b8753d', + creationTime: 1682759805298, + parentThreadID: '92796', + members: [], + roles: {}, + currentUser: { + role: '85172', + permissions: {}, + subscription: { + home: true, + pushNotifs: true, + }, + unread: false, + }, + repliesCount: 0, + containingThreadID: '92796', + community: '1', + sourceMessageID: '93614', + pinnedCount: 0, + }, + rawMessageInfos: [], + truncationStatus: 'exhaustive', + rawEntryInfos: [], + }, + ], + }, + userInfos: { + '256': { id: '256', username: 'ashoat' }, + '83928': { id: '83928', username: 'temp_user3' }, + }, + newMessageInfos: [], + }; + expect(newThreadResponseValidator.is(response)).toBe(true); + expect( + newThreadResponseValidator.is({ + ...response, + newMessageInfos: {}, + }), + ).toBe(false); + }); + + it('should validate thread join response', () => { + const response = { + rawMessageInfos: [ + { + type: 8, + threadID: '93619', + creatorID: '83928', + time: 1682759915935, + id: '93640', + }, + ], + truncationStatuses: {}, + userInfos: { + '256': { id: '256', username: 'ashoat' }, + '83928': { id: '83928', username: 'temp_user3' }, + }, + updatesResult: { + newUpdates: [], + }, + }; + expect(threadJoinResultValidator.is(response)).toBe(true); + expect( + threadJoinResultValidator.is({ + ...response, + updatesResult: [], + }), + ).toBe(false); + }); + + it('should validate thread fetch media response', () => { + const response = { + media: [ + { + type: 'photo', + id: '93642', + uri: 'http://0.0.0.0:3000/comm/upload/93642/1e0d7a5262952e3b', + dimensions: { width: 220, height: 220 }, + }, + ], + }; + expect(threadFetchMediaResultValidator.is(response)).toBe(true); + expect( + threadFetchMediaResultValidator.is({ ...response, media: undefined }), + ).toBe(false); + }); + + it('should validate toggle message pin response', () => { + const response = { threadID: '123', newMessageInfos: [] }; + expect(toggleMessagePinResultValidator.is(response)).toBe(true); + expect( + toggleMessagePinResultValidator.is({ ...response, threadID: undefined }), + ).toBe(false); + }); +}); + +describe('message responders', () => { + it('should validate send message response', () => { + const response = { + newMessageInfo: { + type: 0, + threadID: '93619', + creatorID: '83928', + time: 1682761023640, + text: 'a', + localID: 'local3', + id: '93649', + }, + }; + expect(sendMessageResponseValidator.is(response)).toBe(true); + expect( + sendMessageResponseValidator.is({ + ...response, + newMEssageInfos: undefined, + }), + ).toBe(false); + }); + + it('should validate fetch message infos response', () => { + const response = { + rawMessageInfos: [ + { + type: 0, + id: '83954', + threadID: '83938', + time: 1673561155110, + creatorID: '256', + text: 'welcome to Comm!', + }, + ], + truncationStatuses: { '83938': 'exhaustive' }, + userInfos: { + '256': { id: '256', username: 'ashoat' }, + '83928': { id: '83928', username: 'temp_user3' }, + }, + }; + expect(fetchMessageInfosResponseValidator.is(response)).toBe(true); + expect( + fetchMessageInfosResponseValidator.is({ + ...response, + userInfos: undefined, + }), + ).toBe(false); + }); + + it('should validate send edit message response', () => { + const response = { + newMessageInfos: [ + { + type: 0, + id: '83954', + threadID: '83938', + time: 1673561155110, + creatorID: '256', + text: 'welcome to Comm!', + }, + ], + }; + expect(sendEditMessageResponseValidator.is(response)).toBe(true); + expect( + sendEditMessageResponseValidator.is({ + ...response, + newMessageInfos: undefined, + }), + ).toBe(false); + }); + + it('should validate fetch pinned message response', () => { + const response = { + pinnedMessages: [ + { + type: 0, + id: '83954', + threadID: '83938', + time: 1673561155110, + creatorID: '256', + text: 'welcome to Comm!', + }, + ], + }; + expect(fetchPinnedMessagesResultValidator.is(response)).toBe(true); + expect( + fetchPinnedMessagesResultValidator.is({ + ...response, + pinnedMessages: undefined, + }), + ).toBe(false); + }); +}); + +describe('report responders', () => { + it('should validate report creation response', () => { + const response = { id: '123' }; + expect(reportCreationResponseValidator.is(response)).toBe(true); + expect(reportCreationResponseValidator.is({})).toBe(false); + }); +}); diff --git a/keyserver/src/responders/thread-responders.js b/keyserver/src/responders/thread-responders.js --- a/keyserver/src/responders/thread-responders.js +++ b/keyserver/src/responders/thread-responders.js @@ -1,8 +1,14 @@ // @flow import t from 'tcomb'; -import type { TUnion } from 'tcomb'; +import type { TInterface, TUnion } from 'tcomb'; +import { rawEntryInfoValidator } from 'lib/types/entry-types.js'; +import { mediaValidator } from 'lib/types/media-types.js'; +import { + rawMessageInfoValidator, + messageTruncationStatusesValidator, +} from 'lib/types/message-types.js'; import { type ThreadDeletionRequest, type RoleChangeRequest, @@ -20,7 +26,10 @@ type ToggleMessagePinRequest, type ToggleMessagePinResult, threadTypes, + rawThreadInfoValidator, } from 'lib/types/thread-types.js'; +import { serverUpdateInfoValidator } from 'lib/types/update-types.js'; +import { userInfosValidator } from 'lib/types/user-types.js'; import { updateUserAvatarRequestValidator } from 'lib/utils/avatar-utils.js'; import { values } from 'lib/utils/objects.js'; import { @@ -28,6 +37,7 @@ tNumEnum, tColor, tPassword, + tID, } from 'lib/utils/validation-utils.js'; import { @@ -53,6 +63,14 @@ accountPassword: t.maybe(tPassword), }); +export const leaveThreadResultValidator: TInterface = + tShape({ + threadInfos: t.maybe(t.dict(tID, rawThreadInfoValidator)), + updatesResult: tShape({ + newUpdates: t.list(serverUpdateInfoValidator), + }), + }); + async function threadDeletionResponder( viewer: Viewer, input: any, @@ -71,6 +89,16 @@ }), }); +export const changeThreadSettingsResultValidator: TInterface = + tShape({ + newMessageInfos: t.list(rawMessageInfoValidator), + threadInfo: t.maybe(rawThreadInfoValidator), + threadInfos: t.maybe(t.dict(tID, rawThreadInfoValidator)), + updatesResult: tShape({ + newUpdates: t.list(serverUpdateInfoValidator), + }), + }); + async function roleUpdateResponder( viewer: Viewer, input: any, @@ -154,6 +182,18 @@ ...threadRequestValidationShape, }), ]); + +export const newThreadResponseValidator: TInterface = + tShape({ + updatesResult: tShape({ + newUpdates: t.list(serverUpdateInfoValidator), + }), + newMessageInfos: t.list(rawMessageInfoValidator), + newThreadInfo: t.maybe(rawThreadInfoValidator), + userInfos: userInfosValidator, + newThreadID: t.maybe(tID), + }); + async function threadCreationResponder( viewer: Viewer, input: any, @@ -171,6 +211,19 @@ calendarQuery: t.maybe(entryQueryInputValidator), inviteLinkSecret: t.maybe(t.String), }); + +export const threadJoinResultValidator: TInterface = + tShape({ + threadInfos: t.maybe(t.dict(tID, rawThreadInfoValidator)), + updatesResult: tShape({ + newUpdates: t.list(serverUpdateInfoValidator), + }), + rawMessageInfos: t.list(rawMessageInfoValidator), + truncationStatuses: messageTruncationStatusesValidator, + userInfos: userInfosValidator, + rawEntryInfos: t.maybe(t.list(rawEntryInfoValidator)), + }); + async function threadJoinResponder( viewer: Viewer, input: any, @@ -190,6 +243,10 @@ limit: t.Number, offset: t.Number, }); + +export const threadFetchMediaResultValidator: TInterface = + tShape({ media: t.list(mediaValidator) }); + async function threadFetchMediaResponder( viewer: Viewer, input: any, @@ -203,6 +260,13 @@ messageID: t.String, action: t.enums.of(['pin', 'unpin']), }); + +export const toggleMessagePinResultValidator: TInterface = + tShape({ + newMessageInfos: t.list(rawMessageInfoValidator), + threadID: tID, + }); + async function toggleMessagePinResponder( viewer: Viewer, input: any, diff --git a/lib/types/report-types.js b/lib/types/report-types.js --- a/lib/types/report-types.js +++ b/lib/types/report-types.js @@ -1,6 +1,7 @@ // @flow import invariant from 'invariant'; +import t, { type TInterface } from 'tcomb'; import { type PlatformDetails } from './device-types.js'; import { type RawEntryInfo, type CalendarQuery } from './entry-types.js'; @@ -8,6 +9,7 @@ import type { AppState, BaseAction } from './redux-types.js'; import { type RawThreadInfo } from './thread-types.js'; import type { UserInfo, UserInfos } from './user-types.js'; +import { tPlatformDetails, tShape } from '../utils/validation-utils.js'; export type EnabledReports = { +crashReports: boolean, @@ -188,6 +190,13 @@ +creationTime: number, }; +export const reportInfoValidator: TInterface = tShape({ + id: t.String, + viewerID: t.String, + platformDetails: tPlatformDetails, + creationTime: t.Number, +}); + export type FetchErrorReportInfosRequest = { +cursor: ?string, }; diff --git a/lib/types/user-types.js b/lib/types/user-types.js --- a/lib/types/user-types.js +++ b/lib/types/user-types.js @@ -1,6 +1,6 @@ // @flow -import t, { type TInterface } from 'tcomb'; +import t, { type TInterface, type TDict } from 'tcomb'; import { type DefaultNotificationPayload, @@ -45,6 +45,10 @@ avatar: t.maybe(clientAvatarValidator), }); export type UserInfos = { +[id: string]: UserInfo }; +export const userInfosValidator: TDict = t.dict( + t.String, + userInfoValidator, +); export type AccountUserInfo = { +id: string,