343 lines
		
	
	
	
		
			9.1 KiB
			
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			343 lines
		
	
	
	
		
			9.1 KiB
			
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
// Copyright 2021 Signal Messenger, LLC
 | 
						|
// SPDX-License-Identifier: AGPL-3.0-only
 | 
						|
 | 
						|
import { assert } from 'chai';
 | 
						|
import { LibSignalErrorBase } from '@signalapp/libsignal-client';
 | 
						|
 | 
						|
import {
 | 
						|
  _analyzeSenderKeyDevices,
 | 
						|
  _shouldFailSend,
 | 
						|
} from '../../util/sendToGroup';
 | 
						|
import { generateAci } from '../../types/ServiceId';
 | 
						|
 | 
						|
import type { DeviceType } from '../../textsecure/Types.d';
 | 
						|
import {
 | 
						|
  ConnectTimeoutError,
 | 
						|
  HTTPError,
 | 
						|
  IncorrectSenderKeyAuthError,
 | 
						|
  MessageError,
 | 
						|
  OutgoingIdentityKeyError,
 | 
						|
  OutgoingMessageError,
 | 
						|
  SendMessageChallengeError,
 | 
						|
  SendMessageNetworkError,
 | 
						|
  SendMessageProtoError,
 | 
						|
  UnknownRecipientError,
 | 
						|
  UnregisteredUserError,
 | 
						|
} from '../../textsecure/Errors';
 | 
						|
 | 
						|
describe('sendToGroup', () => {
 | 
						|
  const serviceIdOne = generateAci();
 | 
						|
  const serviceIdTwo = generateAci();
 | 
						|
 | 
						|
  describe('#_analyzeSenderKeyDevices', () => {
 | 
						|
    function getDefaultDeviceList(): Array<DeviceType> {
 | 
						|
      return [
 | 
						|
        {
 | 
						|
          serviceId: serviceIdOne,
 | 
						|
          id: 1,
 | 
						|
          registrationId: 11,
 | 
						|
        },
 | 
						|
        {
 | 
						|
          serviceId: serviceIdOne,
 | 
						|
          id: 2,
 | 
						|
          registrationId: 22,
 | 
						|
        },
 | 
						|
        {
 | 
						|
          serviceId: serviceIdTwo,
 | 
						|
          id: 2,
 | 
						|
          registrationId: 33,
 | 
						|
        },
 | 
						|
      ];
 | 
						|
    }
 | 
						|
 | 
						|
    it('returns nothing if new and previous lists are the same', () => {
 | 
						|
      const memberDevices = getDefaultDeviceList();
 | 
						|
      const devicesForSend = getDefaultDeviceList();
 | 
						|
 | 
						|
      const {
 | 
						|
        newToMemberDevices,
 | 
						|
        newToMemberServiceIds,
 | 
						|
        removedFromMemberDevices,
 | 
						|
        removedFromMemberServiceIds,
 | 
						|
      } = _analyzeSenderKeyDevices(memberDevices, devicesForSend);
 | 
						|
 | 
						|
      assert.isEmpty(newToMemberDevices);
 | 
						|
      assert.isEmpty(newToMemberServiceIds);
 | 
						|
      assert.isEmpty(removedFromMemberDevices);
 | 
						|
      assert.isEmpty(removedFromMemberServiceIds);
 | 
						|
    });
 | 
						|
    it('returns set of new devices', () => {
 | 
						|
      const memberDevices = getDefaultDeviceList();
 | 
						|
      const devicesForSend = getDefaultDeviceList();
 | 
						|
 | 
						|
      memberDevices.pop();
 | 
						|
      memberDevices.pop();
 | 
						|
 | 
						|
      const {
 | 
						|
        newToMemberDevices,
 | 
						|
        newToMemberServiceIds,
 | 
						|
        removedFromMemberDevices,
 | 
						|
        removedFromMemberServiceIds,
 | 
						|
      } = _analyzeSenderKeyDevices(memberDevices, devicesForSend);
 | 
						|
 | 
						|
      assert.deepEqual(newToMemberDevices, [
 | 
						|
        {
 | 
						|
          serviceId: serviceIdOne,
 | 
						|
          id: 2,
 | 
						|
          registrationId: 22,
 | 
						|
        },
 | 
						|
        {
 | 
						|
          serviceId: serviceIdTwo,
 | 
						|
          id: 2,
 | 
						|
          registrationId: 33,
 | 
						|
        },
 | 
						|
      ]);
 | 
						|
      assert.deepEqual(newToMemberServiceIds, [serviceIdOne, serviceIdTwo]);
 | 
						|
      assert.isEmpty(removedFromMemberDevices);
 | 
						|
      assert.isEmpty(removedFromMemberServiceIds);
 | 
						|
    });
 | 
						|
    it('returns set of removed devices', () => {
 | 
						|
      const memberDevices = getDefaultDeviceList();
 | 
						|
      const devicesForSend = getDefaultDeviceList();
 | 
						|
 | 
						|
      devicesForSend.pop();
 | 
						|
      devicesForSend.pop();
 | 
						|
 | 
						|
      const {
 | 
						|
        newToMemberDevices,
 | 
						|
        newToMemberServiceIds,
 | 
						|
        removedFromMemberDevices,
 | 
						|
        removedFromMemberServiceIds,
 | 
						|
      } = _analyzeSenderKeyDevices(memberDevices, devicesForSend);
 | 
						|
 | 
						|
      assert.isEmpty(newToMemberDevices);
 | 
						|
      assert.isEmpty(newToMemberServiceIds);
 | 
						|
      assert.deepEqual(removedFromMemberDevices, [
 | 
						|
        {
 | 
						|
          serviceId: serviceIdOne,
 | 
						|
          id: 2,
 | 
						|
          registrationId: 22,
 | 
						|
        },
 | 
						|
        {
 | 
						|
          serviceId: serviceIdTwo,
 | 
						|
          id: 2,
 | 
						|
          registrationId: 33,
 | 
						|
        },
 | 
						|
      ]);
 | 
						|
      assert.deepEqual(removedFromMemberServiceIds, [
 | 
						|
        serviceIdOne,
 | 
						|
        serviceIdTwo,
 | 
						|
      ]);
 | 
						|
    });
 | 
						|
    it('returns empty removals if partial send', () => {
 | 
						|
      const memberDevices = getDefaultDeviceList();
 | 
						|
      const devicesForSend = getDefaultDeviceList();
 | 
						|
 | 
						|
      devicesForSend.pop();
 | 
						|
      devicesForSend.pop();
 | 
						|
 | 
						|
      const isPartialSend = true;
 | 
						|
      const {
 | 
						|
        newToMemberDevices,
 | 
						|
        newToMemberServiceIds,
 | 
						|
        removedFromMemberDevices,
 | 
						|
        removedFromMemberServiceIds,
 | 
						|
      } = _analyzeSenderKeyDevices(
 | 
						|
        memberDevices,
 | 
						|
        devicesForSend,
 | 
						|
        isPartialSend
 | 
						|
      );
 | 
						|
 | 
						|
      assert.isEmpty(newToMemberDevices);
 | 
						|
      assert.isEmpty(newToMemberServiceIds);
 | 
						|
      assert.isEmpty(removedFromMemberDevices);
 | 
						|
      assert.isEmpty(removedFromMemberServiceIds);
 | 
						|
    });
 | 
						|
  });
 | 
						|
 | 
						|
  describe('#_shouldFailSend', () => {
 | 
						|
    it('returns false for a generic error', async () => {
 | 
						|
      const error = new Error('generic');
 | 
						|
      assert.isFalse(_shouldFailSend(error, 'testing generic'));
 | 
						|
    });
 | 
						|
 | 
						|
    it("returns true for any error with 'untrusted' identity", async () => {
 | 
						|
      const error = new LibSignalErrorBase(
 | 
						|
        'untrusted identity',
 | 
						|
        'UntrustedIdentity',
 | 
						|
        'ignored'
 | 
						|
      );
 | 
						|
      assert.isTrue(_shouldFailSend(error, 'logId'));
 | 
						|
    });
 | 
						|
 | 
						|
    it('returns true for certain types of error subclasses', async () => {
 | 
						|
      assert.isTrue(
 | 
						|
        _shouldFailSend(
 | 
						|
          new OutgoingIdentityKeyError('something'),
 | 
						|
          'testing OutgoingIdentityKeyError'
 | 
						|
        )
 | 
						|
      );
 | 
						|
      assert.isTrue(
 | 
						|
        _shouldFailSend(
 | 
						|
          new UnregisteredUserError(
 | 
						|
            generateAci(),
 | 
						|
            new HTTPError('something', {
 | 
						|
              code: 400,
 | 
						|
              headers: {},
 | 
						|
            })
 | 
						|
          ),
 | 
						|
          'testing UnregisteredUserError'
 | 
						|
        )
 | 
						|
      );
 | 
						|
      assert.isTrue(
 | 
						|
        _shouldFailSend(
 | 
						|
          new ConnectTimeoutError('something'),
 | 
						|
          'testing ConnectTimeoutError'
 | 
						|
        )
 | 
						|
      );
 | 
						|
    });
 | 
						|
 | 
						|
    it('returns false for unspecified error codes', () => {
 | 
						|
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
 | 
						|
      const error: any = new Error('generic');
 | 
						|
 | 
						|
      error.code = 422;
 | 
						|
      assert.isFalse(_shouldFailSend(error, 'testing generic 422'));
 | 
						|
 | 
						|
      error.code = 204;
 | 
						|
      assert.isFalse(_shouldFailSend(error, 'testing generic 204'));
 | 
						|
    });
 | 
						|
 | 
						|
    it('returns false for specific errors', () => {
 | 
						|
      const unknownRecipient = new UnknownRecipientError();
 | 
						|
      assert.isFalse(
 | 
						|
        _shouldFailSend(unknownRecipient, 'testing unknown recipient')
 | 
						|
      );
 | 
						|
 | 
						|
      const incorrectAuth = new IncorrectSenderKeyAuthError();
 | 
						|
      assert.isFalse(_shouldFailSend(incorrectAuth, 'testing incorrect auth'));
 | 
						|
    });
 | 
						|
 | 
						|
    it('returns true for a specified error codes', () => {
 | 
						|
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
 | 
						|
      const error: any = new Error('generic');
 | 
						|
      error.code = 428;
 | 
						|
 | 
						|
      assert.isTrue(_shouldFailSend(error, 'testing generic'));
 | 
						|
      assert.isTrue(
 | 
						|
        _shouldFailSend(
 | 
						|
          new HTTPError('something', {
 | 
						|
            code: 404,
 | 
						|
            headers: {},
 | 
						|
          }),
 | 
						|
          'testing HTTPError'
 | 
						|
        )
 | 
						|
      );
 | 
						|
      assert.isTrue(
 | 
						|
        _shouldFailSend(
 | 
						|
          new OutgoingMessageError(
 | 
						|
            'something',
 | 
						|
            null,
 | 
						|
            null,
 | 
						|
            new HTTPError('something', {
 | 
						|
              code: 413,
 | 
						|
              headers: {},
 | 
						|
            })
 | 
						|
          ),
 | 
						|
          'testing OutgoingMessageError'
 | 
						|
        )
 | 
						|
      );
 | 
						|
      assert.isTrue(
 | 
						|
        _shouldFailSend(
 | 
						|
          new OutgoingMessageError(
 | 
						|
            'something',
 | 
						|
            null,
 | 
						|
            null,
 | 
						|
            new HTTPError('something', {
 | 
						|
              code: 429,
 | 
						|
              headers: {},
 | 
						|
            })
 | 
						|
          ),
 | 
						|
          'testing OutgoingMessageError'
 | 
						|
        )
 | 
						|
      );
 | 
						|
      assert.isTrue(
 | 
						|
        _shouldFailSend(
 | 
						|
          new SendMessageNetworkError(
 | 
						|
            'something',
 | 
						|
            null,
 | 
						|
            new HTTPError('something', {
 | 
						|
              code: 428,
 | 
						|
              headers: {},
 | 
						|
            })
 | 
						|
          ),
 | 
						|
          'testing SendMessageNetworkError'
 | 
						|
        )
 | 
						|
      );
 | 
						|
      assert.isTrue(
 | 
						|
        _shouldFailSend(
 | 
						|
          new SendMessageChallengeError(
 | 
						|
            'something',
 | 
						|
            new HTTPError('something', {
 | 
						|
              code: 500,
 | 
						|
              headers: {},
 | 
						|
            })
 | 
						|
          ),
 | 
						|
          'testing SendMessageChallengeError'
 | 
						|
        )
 | 
						|
      );
 | 
						|
      assert.isTrue(
 | 
						|
        _shouldFailSend(
 | 
						|
          new MessageError(
 | 
						|
            'something',
 | 
						|
            new HTTPError('something', {
 | 
						|
              code: 508,
 | 
						|
              headers: {},
 | 
						|
            })
 | 
						|
          ),
 | 
						|
          'testing MessageError'
 | 
						|
        )
 | 
						|
      );
 | 
						|
    });
 | 
						|
    it('returns true for errors inside of SendMessageProtoError', () => {
 | 
						|
      assert.isTrue(
 | 
						|
        _shouldFailSend(
 | 
						|
          new SendMessageProtoError({
 | 
						|
            dataMessage: undefined,
 | 
						|
            editMessage: undefined,
 | 
						|
          }),
 | 
						|
          'testing missing errors list'
 | 
						|
        )
 | 
						|
      );
 | 
						|
 | 
						|
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
 | 
						|
      const error: any = new Error('generic');
 | 
						|
      error.code = 428;
 | 
						|
 | 
						|
      assert.isTrue(
 | 
						|
        _shouldFailSend(
 | 
						|
          new SendMessageProtoError({
 | 
						|
            dataMessage: undefined,
 | 
						|
            editMessage: undefined,
 | 
						|
            errors: [error],
 | 
						|
          }),
 | 
						|
          'testing one error with code'
 | 
						|
        )
 | 
						|
      );
 | 
						|
 | 
						|
      assert.isTrue(
 | 
						|
        _shouldFailSend(
 | 
						|
          new SendMessageProtoError({
 | 
						|
            dataMessage: undefined,
 | 
						|
            editMessage: undefined,
 | 
						|
            errors: [
 | 
						|
              new Error('something'),
 | 
						|
              new ConnectTimeoutError('something'),
 | 
						|
            ],
 | 
						|
          }),
 | 
						|
          'testing ConnectTimeoutError'
 | 
						|
        )
 | 
						|
      );
 | 
						|
    });
 | 
						|
  });
 | 
						|
});
 |