huridocs/uwazi

View on GitHub
app/api/suggestions/specs/eventListeners.spec.ts

Summary

Maintainability
D
3 days
Test Coverage
import entities from 'api/entities';
import { EntityDeletedEvent } from 'api/entities/events/EntityDeletedEvent';
import { EntityUpdatedEvent } from 'api/entities/events/EntityUpdatedEvent';
import { applicationEventsBus } from 'api/eventsbus';
import { FileCreatedEvent } from 'api/files/events/FileCreatedEvent';
import { FilesDeletedEvent } from 'api/files/events/FilesDeletedEvent';
import { FileUpdatedEvent } from 'api/files/events/FileUpdatedEvent';
import { search } from 'api/search';
import { TemplateDeletedEvent } from 'api/templates/events/TemplateDeletedEvent';
import { TemplateUpdatedEvent } from 'api/templates/events/TemplateUpdatedEvent';
import { getFixturesFactory } from 'api/utils/fixturesFactory';
import db, { DBFixture, testingDB } from 'api/utils/testing_db';
import { propertyTypes } from 'shared/propertyTypes';
import { FileType } from 'shared/types/fileType';
import { UserRole } from 'shared/types/userSchema';
import { registerEventListeners } from '../eventListeners';
import { Suggestions } from '../suggestions';

const fixturesFactory = getFixturesFactory();

const notExtractedTemplateName = 'not_extracted_template';
const extractedTemplateName = 'extracted_template';
const otherExtractedTemplateName = 'other_extracted_template';

const adminUser = {
  username: 'admin',
  role: UserRole.ADMIN,
  email: 'user@test.com',
};

const fixtures: DBFixture = {
  templates: [
    fixturesFactory.template(notExtractedTemplateName, [
      fixturesFactory.property('some_property', propertyTypes.text),
    ]),
    fixturesFactory.template(extractedTemplateName, [
      fixturesFactory.property('not_extracted_property_1', propertyTypes.text),
      fixturesFactory.property('not_extracted_property_2', propertyTypes.numeric),
      fixturesFactory.property('extracted_property_1', propertyTypes.text),
      fixturesFactory.property('extracted_property_2', propertyTypes.numeric),
      fixturesFactory.property('select_property', propertyTypes.select),
      fixturesFactory.property('multiselect_property', propertyTypes.multiselect),
      fixturesFactory.property('relationship_property', propertyTypes.relationship),
    ]),
    fixturesFactory.template(otherExtractedTemplateName, [
      fixturesFactory.property('extracted_property_1', propertyTypes.text),
      fixturesFactory.property('extracted_property_2_1', propertyTypes.text),
      fixturesFactory.property('extracted_property_2_2', propertyTypes.numeric),
    ]),
  ],
  entities: [
    fixturesFactory.entity('ent', extractedTemplateName, {}, { sharedId: 'entity for new file' }),
    fixturesFactory.entity(
      'ent2',
      notExtractedTemplateName,
      {},
      { sharedId: 'entity with template not in config' }
    ),
  ],
  files: [
    fixturesFactory.file('entfile', 'entity for new file', 'document', 'entfile.pdf'),
    fixturesFactory.file(
      'entfile2',
      'entity with template not in config',
      'document',
      'entfile2.pdf'
    ),
  ],
  settings: [
    {
      _id: db.id(),
      languages: [{ key: 'en', default: true, label: 'English' }],
      features: {
        metadataExtraction: {
          url: 'service-url',
        },
      },
    },
  ],
  ixextractors: [
    fixturesFactory.ixExtractor('title_extractor', 'title', [
      extractedTemplateName,
      otherExtractedTemplateName,
    ]),
    fixturesFactory.ixExtractor('extractor1', 'extracted_property_1', [
      extractedTemplateName,
      otherExtractedTemplateName,
    ]),
    fixturesFactory.ixExtractor('extractor2', 'extracted_property_2', [extractedTemplateName]),
    fixturesFactory.ixExtractor('extractor3', 'some_property', ['some_other_template']),
    fixturesFactory.ixExtractor('extractor4', 'extracted_property_2_1', [
      otherExtractedTemplateName,
    ]),
    fixturesFactory.ixExtractor('extractor5', 'extracted_property_2_2', [
      otherExtractedTemplateName,
    ]),
    fixturesFactory.ixExtractor('extractor6', 'select_property', [extractedTemplateName]),
    fixturesFactory.ixExtractor('extractor7', 'multiselect_property', [extractedTemplateName]),
    fixturesFactory.ixExtractor('extractor8', 'relationship_property', [extractedTemplateName]),
  ],
  ixsuggestions: [
    fixturesFactory.ixSuggestion(
      'new_prop_1_suggestion',
      'extractor1',
      'entity for new file',
      extractedTemplateName,
      'entfile',
      'extracted_property_1'
    ),
    fixturesFactory.ixSuggestion(
      'new_prop_2_suggestion',
      'extractor2',
      'entity for new file',
      extractedTemplateName,
      'entfile',
      'extracted_property_2'
    ),
    fixturesFactory.ixSuggestion(
      'new_title_suggestion',
      'title_extractor',
      'entity for new file',
      extractedTemplateName,
      'entfile',
      'title'
    ),
    fixturesFactory.ixSuggestion(
      'new_select_suggestion',
      'extractor6',
      'entity for new file',
      extractedTemplateName,
      'entfile',
      'select_property'
    ),
    fixturesFactory.ixSuggestion(
      'new_multiselect_suggestion',
      'extractor7',
      'entity for new file',
      extractedTemplateName,
      'entfile',
      'multiselect_property'
    ),
    fixturesFactory.ixSuggestion(
      'new_relationship_suggestion',
      'extractor8',
      'entity for new file',
      extractedTemplateName,
      'entfile',
      'relationship_property'
    ),
  ],
};

const disableFeatures = async () =>
  testingDB.mongodb?.collection('settings').updateOne({}, { $set: { features: {} } });

beforeAll(() => {
  registerEventListeners(applicationEventsBus);
});

beforeEach(async () => {
  jest.spyOn(search, 'indexEntities').mockReturnValue(Promise.resolve());
  await db.setupFixturesAndContext(fixtures);
});

afterAll(async () => {
  await db.disconnect();
});

describe(`On ${EntityUpdatedEvent.name}`, () => {
  let updateSpy: jest.SpyInstance;
  const extractedBefore = {
    _id: db.id(),
    sharedId: 'sid',
    title: 'title',
    template: fixturesFactory.id(extractedTemplateName),
    metadata: {
      not_extracted_property_1: [{ value: 'text' }],
      not_extracted_property_2: [{ value: 0 }],
      extracted_property_1: [{ value: 'text' }],
      extracted_property_2: [{ value: 0 }],
    },
    language: 'en',
  };

  beforeAll(async () => {
    updateSpy = jest.spyOn(Suggestions, 'updateStates');
  });

  beforeEach(() => {
    updateSpy.mockClear();
  });

  afterAll(() => {
    updateSpy.mockRestore();
  });

  it('should not act if the feature is not configured', async () => {
    await disableFeatures();

    await applicationEventsBus.emit(
      new EntityUpdatedEvent({
        before: [extractedBefore],
        after: [
          {
            ...extractedBefore.metadata,
            extracted_property_1: [{ value: 'new text' }],
          },
        ],
        targetLanguageKey: 'en',
      })
    );

    expect(updateSpy).not.toHaveBeenCalled();
  });

  it.each([
    {
      before: {
        _id: db.id(),
        sharedId: 'sid',
        title: 'title',
        template: fixturesFactory.id(notExtractedTemplateName),
        metadata: { some_property: [{ value: 'text' }] },
        language: 'en',
      },
      afterMetadata: { some_property: [{ value: 'other_text' }] },
      called: false,
      message: 'should not update suggestions, if template is not set up',
    },
    {
      before: extractedBefore,
      afterMetadata: extractedBefore.metadata,
      called: false,
      message: 'should not update suggestions, if no metadata changed',
    },
    {
      before: extractedBefore,
      afterMetadata: {
        ...extractedBefore.metadata,
        not_extracted_property_1: [{ value: 'new text' }],
        not_extracted_property_2: [{ value: 1 }],
      },
      called: false,
      message: 'should not update suggestions, if no relevant metadata changed',
    },
    {
      before: extractedBefore,
      afterMetadata: {
        ...extractedBefore.metadata,
        extracted_property_1: [{ value: 'new text' }],
      },
      called: true,
      message: 'should update suggestions, if only relevant metadata changed',
    },
    {
      before: extractedBefore,
      afterMetadata: {
        not_extracted_property_1: [{ value: 'new text' }],
        not_extracted_property_2: [{ value: 1 }],
        extracted_property_1: [{ value: 'new text' }],
        extracted_property_2: [{ value: 1 }],
      },
      called: true,
      message: 'should update suggestions, if relevant metadata is also changed',
    },
  ])('$message', async ({ before, afterMetadata, called }) => {
    const after = {
      ...before,
      metadata: afterMetadata,
    };
    await applicationEventsBus.emit(
      new EntityUpdatedEvent({ before: [before], after: [after], targetLanguageKey: 'en' })
    );
    if (called) {
      expect(updateSpy).toHaveBeenCalled();
    } else {
      expect(updateSpy).not.toHaveBeenCalled();
    }
  });

  it('should update suggestions, if title is configured and changed', async () => {
    const after = {
      ...extractedBefore,
      title: 'new title',
    };
    await applicationEventsBus.emit(
      new EntityUpdatedEvent({ before: [extractedBefore], after: [after], targetLanguageKey: 'en' })
    );
    expect(updateSpy).toHaveBeenCalled();
  });

  it.each([
    {
      case: 'should not update suggestions if template is not changed',
      sharedId: 'entity for new file',
      newTemplate: fixturesFactory.id(extractedTemplateName),
      expectedSuggestions: [
        {
          entityId: 'entity for new file',
          entityTemplate: fixturesFactory.id(extractedTemplateName).toString(),
          propertyName: 'extracted_property_1',
          fileId: fixturesFactory.id('entfile'),
        },
        {
          entityId: 'entity for new file',
          entityTemplate: fixturesFactory.id(extractedTemplateName).toString(),
          propertyName: 'extracted_property_2',
          fileId: fixturesFactory.id('entfile'),
        },
        {
          entityId: 'entity for new file',
          entityTemplate: fixturesFactory.id(extractedTemplateName).toString(),
          propertyName: 'multiselect_property',
          fileId: fixturesFactory.id('entfile'),
        },
        {
          entityId: 'entity for new file',
          entityTemplate: fixturesFactory.id(extractedTemplateName).toString(),
          propertyName: 'relationship_property',
          fileId: fixturesFactory.id('entfile'),
        },
        {
          entityId: 'entity for new file',
          entityTemplate: fixturesFactory.id(extractedTemplateName).toString(),
          propertyName: 'select_property',
          fileId: fixturesFactory.id('entfile'),
        },
        {
          entityId: 'entity for new file',
          entityTemplate: fixturesFactory.id(extractedTemplateName).toString(),
          propertyName: 'title',
          fileId: fixturesFactory.id('entfile'),
        },
      ],
    },
    {
      case: 'should update suggestions if template is changed from configured to not configured',
      sharedId: 'entity for new file',
      newTemplate: fixturesFactory.id(notExtractedTemplateName),
      expectedSuggestions: [],
    },
    {
      case: 'should create suggestions if template is changed from not configured to configured',
      sharedId: 'entity with template not in config',
      newTemplate: fixturesFactory.id(extractedTemplateName),
      expectedSuggestions: [
        {
          entityId: 'entity for new file',
          entityTemplate: fixturesFactory.id(extractedTemplateName).toString(),
          propertyName: 'extracted_property_1',
          fileId: fixturesFactory.id('entfile'),
        },
        {
          entityId: 'entity with template not in config',
          entityTemplate: fixturesFactory.id(extractedTemplateName).toString(),
          propertyName: 'extracted_property_1',
          fileId: fixturesFactory.id('entfile2'),
        },
        {
          entityId: 'entity for new file',
          entityTemplate: fixturesFactory.id(extractedTemplateName).toString(),
          propertyName: 'extracted_property_2',
          fileId: fixturesFactory.id('entfile'),
        },
        {
          entityId: 'entity with template not in config',
          entityTemplate: fixturesFactory.id(extractedTemplateName).toString(),
          propertyName: 'extracted_property_2',
          fileId: fixturesFactory.id('entfile2'),
        },
        {
          entityId: 'entity for new file',
          entityTemplate: fixturesFactory.id(extractedTemplateName).toString(),
          propertyName: 'multiselect_property',
          fileId: fixturesFactory.id('entfile'),
        },
        {
          entityId: 'entity with template not in config',
          entityTemplate: fixturesFactory.id(extractedTemplateName).toString(),
          propertyName: 'multiselect_property',
          fileId: fixturesFactory.id('entfile2'),
        },
        {
          entityId: 'entity for new file',
          entityTemplate: fixturesFactory.id(extractedTemplateName).toString(),
          propertyName: 'relationship_property',
          fileId: fixturesFactory.id('entfile'),
        },
        {
          entityId: 'entity with template not in config',
          entityTemplate: fixturesFactory.id(extractedTemplateName).toString(),
          propertyName: 'relationship_property',
          fileId: fixturesFactory.id('entfile2'),
        },
        {
          entityId: 'entity for new file',
          entityTemplate: fixturesFactory.id(extractedTemplateName).toString(),
          propertyName: 'select_property',
          fileId: fixturesFactory.id('entfile'),
        },
        {
          entityId: 'entity with template not in config',
          entityTemplate: fixturesFactory.id(extractedTemplateName).toString(),
          propertyName: 'select_property',
          fileId: fixturesFactory.id('entfile2'),
        },
        {
          entityId: 'entity for new file',
          entityTemplate: fixturesFactory.id(extractedTemplateName).toString(),
          propertyName: 'title',
          fileId: fixturesFactory.id('entfile'),
        },
        {
          entityId: 'entity with template not in config',
          entityTemplate: fixturesFactory.id(extractedTemplateName).toString(),
          propertyName: 'title',
          fileId: fixturesFactory.id('entfile2'),
        },
      ],
    },
    {
      case: 'should update suggestions if template is changed from configured to another configured',
      sharedId: 'entity for new file',
      newTemplate: fixturesFactory.id(otherExtractedTemplateName),
      expectedSuggestions: [
        {
          entityId: 'entity for new file',
          entityTemplate: fixturesFactory.id(otherExtractedTemplateName).toString(),
          propertyName: 'extracted_property_1',
          fileId: fixturesFactory.id('entfile'),
        },
        {
          entityId: 'entity for new file',
          entityTemplate: fixturesFactory.id(otherExtractedTemplateName).toString(),
          propertyName: 'extracted_property_2_1',
          fileId: fixturesFactory.id('entfile'),
        },
        {
          entityId: 'entity for new file',
          entityTemplate: fixturesFactory.id(otherExtractedTemplateName).toString(),
          propertyName: 'extracted_property_2_2',
          fileId: fixturesFactory.id('entfile'),
        },
        {
          entityId: 'entity for new file',
          entityTemplate: fixturesFactory.id(otherExtractedTemplateName).toString(),
          propertyName: 'title',
          fileId: fixturesFactory.id('entfile'),
        },
      ],
    },
  ])('$case', async ({ sharedId, newTemplate, expectedSuggestions }) => {
    const current = await entities.getById(sharedId, 'en');
    const toSave = { ...current, template: newTemplate };
    await entities.save(toSave, { user: adminUser, language: 'en' });
    const allSuggestions =
      (await db.mongodb
        ?.collection('ixsuggestions')
        .find({}, { sort: { propertyName: 1 } })
        .toArray()) || [];
    expect(allSuggestions).toHaveLength(expectedSuggestions.length);
    expect(allSuggestions).toMatchObject(expectedSuggestions);
  });
});

describe(`On ${EntityDeletedEvent.name}`, () => {
  it.each([
    {
      message: 'should not act if the feature is not enabled',
      featureEnabled: false,
    },
    {
      message: 'should delete all suggestions related to entities that triggered the event',
      featureEnabled: true,
      calledWith: 'shared',
    },
  ])('$message', async ({ featureEnabled, calledWith }) => {
    if (!featureEnabled) {
      await disableFeatures();
    }

    const deleteSpy = jest.spyOn(Suggestions, 'deleteByEntityId');

    const doc1Id = db.id();
    const doc2Id = db.id();

    await applicationEventsBus.emit(
      new EntityDeletedEvent({
        entity: [
          {
            _id: doc1Id,
            sharedId: 'shared',
          },
          {
            _id: doc2Id,
            sharedId: 'shared',
          },
        ],
      })
    );

    if (calledWith) {
      expect(deleteSpy).toHaveBeenCalledWith(calledWith);
    } else {
      expect(deleteSpy).not.toHaveBeenCalled();
    }
    deleteSpy.mockRestore();
  });
});

describe(`On ${FileCreatedEvent.name}`, () => {
  it('should not act if the feature is not enabled', async () => {
    await disableFeatures();

    const saveSpy = jest.spyOn(Suggestions, 'saveMultiple');

    const fileInfo = fixturesFactory.file(
      'new file',
      'entity for new file',
      'document',
      'new_file.pdf'
    );

    await applicationEventsBus.emit(
      new FileCreatedEvent({
        newFile: fileInfo,
      })
    );

    expect(saveSpy).not.toHaveBeenCalled();
  });

  it('should create blank suggestions, if the new file is an entity document', async () => {
    const saveSpy = jest.spyOn(Suggestions, 'saveMultiple');

    const fileInfo = fixturesFactory.file(
      'new file',
      'entity for new file',
      'document',
      'new_file.pdf'
    );

    await applicationEventsBus.emit(
      new FileCreatedEvent({
        newFile: fileInfo,
      })
    );

    expect(saveSpy).toHaveBeenCalledWith([
      {
        date: expect.any(Number),
        entityId: 'entity for new file',
        entityTemplate: fixturesFactory.id(extractedTemplateName).toString(),
        error: '',
        fileId: fixturesFactory.id('new file'),
        language: 'en',
        propertyName: 'title',
        extractorId: fixturesFactory.id('title_extractor'),
        segment: '',
        status: 'ready',
        suggestedValue: '',
      },
      {
        date: expect.any(Number),
        entityId: 'entity for new file',
        entityTemplate: fixturesFactory.id(extractedTemplateName).toString(),
        error: '',
        fileId: fixturesFactory.id('new file'),
        language: 'en',
        propertyName: 'extracted_property_1',
        extractorId: fixturesFactory.id('extractor1'),
        segment: '',
        status: 'ready',
        suggestedValue: '',
      },
      {
        date: expect.any(Number),
        entityId: 'entity for new file',
        entityTemplate: fixturesFactory.id(extractedTemplateName).toString(),
        error: '',
        fileId: fixturesFactory.id('new file'),
        language: 'en',
        propertyName: 'extracted_property_2',
        extractorId: fixturesFactory.id('extractor2'),
        segment: '',
        status: 'ready',
        suggestedValue: '',
      },
      {
        date: expect.any(Number),
        entityId: 'entity for new file',
        entityTemplate: fixturesFactory.id(extractedTemplateName).toString(),
        error: '',
        fileId: fixturesFactory.id('new file'),
        language: 'en',
        propertyName: 'select_property',
        extractorId: fixturesFactory.id('extractor6'),
        segment: '',
        status: 'ready',
        suggestedValue: '',
      },
      {
        date: expect.any(Number),
        entityId: 'entity for new file',
        entityTemplate: fixturesFactory.id(extractedTemplateName).toString(),
        error: '',
        fileId: fixturesFactory.id('new file'),
        language: 'en',
        propertyName: 'multiselect_property',
        extractorId: fixturesFactory.id('extractor7'),
        segment: '',
        status: 'ready',
        suggestedValue: [],
      },
      {
        date: expect.any(Number),
        entityId: 'entity for new file',
        entityTemplate: fixturesFactory.id(extractedTemplateName).toString(),
        error: '',
        fileId: fixturesFactory.id('new file'),
        language: 'en',
        propertyName: 'relationship_property',
        extractorId: fixturesFactory.id('extractor8'),
        segment: '',
        status: 'ready',
        suggestedValue: [],
      },
    ]);

    saveSpy.mockRestore();
  });

  it('should not fail on not configured templates', async () => {
    const saveSpy = jest.spyOn(Suggestions, 'saveMultiple');

    const fileInfo = fixturesFactory.file(
      'new file',
      'entity with template not in config',
      'document',
      'new_file.pdf'
    );

    await applicationEventsBus.emit(
      new FileCreatedEvent({
        newFile: fileInfo,
      })
    );

    expect(saveSpy).not.toHaveBeenCalled();

    saveSpy.mockRestore();
  });
});

describe(`On ${FileUpdatedEvent.name}`, () => {
  const fileId = db.id();

  const extractedMetadata = {
    extractedMetadata: [
      {
        name: 'propertyName',
        selection: {
          text: 'something',
          selectionRectangles: [{ top: 0, left: 0, width: 0, height: 0, page: '1' }],
        },
      },
    ],
  };

  const original: FileType = {
    _id: fileId,
    creationDate: 1,
    entity: 'sharedId1',
    generatedToc: true,
    originalname: 'upload1',
    type: 'custom',
    language: 'eng',
  };

  it('should not update the ix suggestion state if the extractedMetadata does not change', async () => {
    const updateSpy = jest.spyOn(Suggestions, 'updateStates');

    await applicationEventsBus.emit(new FileUpdatedEvent({ before: original, after: original }));

    expect(updateSpy).not.toHaveBeenCalled();

    updateSpy.mockClear();

    await applicationEventsBus.emit(
      new FileUpdatedEvent({
        before: { ...original, ...extractedMetadata },
        after: { ...original, ...extractedMetadata },
      })
    );

    expect(updateSpy).not.toHaveBeenCalled();
    updateSpy.mockRestore();
  });

  it('should not act if the feature is not enabled', async () => {
    await disableFeatures();
    const updateSpy = jest.spyOn(Suggestions, 'updateStates');

    await applicationEventsBus.emit(
      new FileUpdatedEvent({ before: original, after: { ...original, ...extractedMetadata } })
    );

    expect(updateSpy).not.toHaveBeenCalled();
  });

  it('should update ix suggestions if extractedMetadata changes', async () => {
    const updateSpy = jest.spyOn(Suggestions, 'updateStates');

    await applicationEventsBus.emit(
      new FileUpdatedEvent({ before: original, after: { ...original, ...extractedMetadata } })
    );

    expect(updateSpy).toHaveBeenCalledWith({ fileId });

    updateSpy.mockClear();

    await applicationEventsBus.emit(
      new FileUpdatedEvent({
        before: { ...original, ...extractedMetadata },
        after: {
          ...original,
          extractedMetadata: [
            { ...extractedMetadata.extractedMetadata[0], name: 'other something' },
          ],
        },
      })
    );

    expect(updateSpy).toHaveBeenCalledWith({ fileId });

    updateSpy.mockClear();

    await applicationEventsBus.emit(
      new FileUpdatedEvent({
        before: { ...original, ...extractedMetadata },
        after: {
          ...original,
          extractedMetadata: [],
        },
      })
    );

    expect(updateSpy).toHaveBeenCalledWith({ fileId });
    updateSpy.mockRestore();
  });
});

describe(`On ${FilesDeletedEvent.name}`, () => {
  it.each([
    {
      message: 'should delete all suggestions related to files that triggered the event',
      enabled: true,
    },
    {
      message: 'should not act if the feature is not enabled',
      enabled: false,
    },
  ])('$message', async ({ enabled }) => {
    if (!enabled) {
      await disableFeatures();
    }
    const deleteSpy = jest.spyOn(Suggestions, 'delete');

    const file1Id = db.id();
    const file2Id = db.id();

    await applicationEventsBus.emit(
      new FilesDeletedEvent({
        files: [
          {
            _id: file1Id,
            creationDate: 1,
            entity: 'sharedId1',
            generatedToc: true,
            originalname: 'upload1',
            type: 'document',
            language: 'eng',
          },
          {
            _id: file2Id,
            creationDate: 1,
            entity: 'sharedId2',
            generatedToc: true,
            originalname: 'upload2',
            type: 'document',
            language: 'eng',
          },
        ],
      })
    );

    if (enabled) {
      expect(deleteSpy).toHaveBeenCalledWith({ fileId: { $in: [file1Id, file2Id] } });
    } else {
      expect(deleteSpy).not.toHaveBeenCalled();
    }

    deleteSpy.mockRestore();
  });
});

describe(`On ${TemplateUpdatedEvent.name}`, () => {
  it('should not act if the feature is not enabled', async () => {
    await disableFeatures();

    const extractors = await testingDB.mongodb?.collection('ixextractors').find({}).toArray();
    const suggestions = await testingDB.mongodb?.collection('ixsuggestions').find({}).toArray();

    await applicationEventsBus.emit(
      new TemplateUpdatedEvent({
        before: {
          _id: fixturesFactory.id(extractedTemplateName),
          name: extractedTemplateName,
          properties: [
            fixturesFactory.property('not_extracted_property_1', propertyTypes.text),
            fixturesFactory.property('not_extracted_property_2', propertyTypes.numeric),
            fixturesFactory.property('extracted_property_1', propertyTypes.text),
            fixturesFactory.property('extracted_property_2', propertyTypes.numeric),
          ],
        },
        after: {
          _id: fixturesFactory.id(extractedTemplateName),
          name: extractedTemplateName,
          properties: [
            fixturesFactory.property('not_extracted_property_1', propertyTypes.text),
            fixturesFactory.property('not_extracted_property_2', propertyTypes.numeric),
            fixturesFactory.property('extracted_property_1', propertyTypes.text),
          ],
        },
      })
    );

    expect(extractors).toEqual(
      await testingDB.mongodb?.collection('ixextractors').find({}).toArray()
    );
    expect(suggestions).toEqual(
      await testingDB.mongodb?.collection('ixsuggestions').find({}).toArray()
    );
  });

  it('should delete the template from the extractor if the property not longer exists', async () => {
    await applicationEventsBus.emit(
      new TemplateUpdatedEvent({
        before: {
          _id: fixturesFactory.id(extractedTemplateName),
          name: extractedTemplateName,
          properties: [
            fixturesFactory.property('not_extracted_property_1', propertyTypes.text),
            fixturesFactory.property('not_extracted_property_2', propertyTypes.numeric),
            fixturesFactory.property('extracted_property_1', propertyTypes.text),
            fixturesFactory.property('extracted_property_2', propertyTypes.numeric),
            fixturesFactory.property('select_property', propertyTypes.select),
            fixturesFactory.property('multiselect_property', propertyTypes.multiselect),
            fixturesFactory.property('relationship_property', propertyTypes.relationship),
          ],
        },
        after: {
          _id: fixturesFactory.id(extractedTemplateName),
          name: extractedTemplateName,
          properties: [
            fixturesFactory.property('not_extracted_property_1', propertyTypes.text),
            fixturesFactory.property('not_extracted_property_2', propertyTypes.numeric),
            fixturesFactory.property('extracted_property_1', propertyTypes.text),
            fixturesFactory.property('select_property', propertyTypes.select),
            fixturesFactory.property('multiselect_property', propertyTypes.multiselect),
          ],
        },
      })
    );

    const extractors = await testingDB.mongodb?.collection('ixextractors').find({}).toArray();

    expect(extractors).toEqual([
      fixturesFactory.ixExtractor('title_extractor', 'title', [
        extractedTemplateName,
        otherExtractedTemplateName,
      ]),
      fixturesFactory.ixExtractor('extractor1', 'extracted_property_1', [
        extractedTemplateName,
        otherExtractedTemplateName,
      ]),
      fixturesFactory.ixExtractor('extractor2', 'extracted_property_2', []),
      fixturesFactory.ixExtractor('extractor3', 'some_property', ['some_other_template']),
      fixturesFactory.ixExtractor('extractor4', 'extracted_property_2_1', [
        otherExtractedTemplateName,
      ]),
      fixturesFactory.ixExtractor('extractor5', 'extracted_property_2_2', [
        otherExtractedTemplateName,
      ]),
      fixturesFactory.ixExtractor('extractor6', 'select_property', [extractedTemplateName]),
      fixturesFactory.ixExtractor('extractor7', 'multiselect_property', [extractedTemplateName]),
      fixturesFactory.ixExtractor('extractor8', 'relationship_property', []),
    ]);

    const suggestions = await testingDB.mongodb?.collection('ixsuggestions').find({}).toArray();

    expect(suggestions).toMatchObject([
      {
        entityId: 'entity for new file',
        entityTemplate: fixturesFactory.id(extractedTemplateName).toString(),
        propertyName: 'extracted_property_1',
        extractorId: fixturesFactory.id('extractor1'),
      },
      {
        entityId: 'entity for new file',
        entityTemplate: fixturesFactory.id(extractedTemplateName).toString(),
        propertyName: 'title',
        extractorId: fixturesFactory.id('title_extractor'),
      },
      {
        entityId: 'entity for new file',
        entityTemplate: fixturesFactory.id(extractedTemplateName).toString(),
        propertyName: 'select_property',
        extractorId: fixturesFactory.id('extractor6'),
      },
      {
        entityId: 'entity for new file',
        entityTemplate: fixturesFactory.id(extractedTemplateName).toString(),
        propertyName: 'multiselect_property',
        extractorId: fixturesFactory.id('extractor7'),
      },
    ]);
  });

  it('should remove the template from the extractor if the property changed names', async () => {
    await applicationEventsBus.emit(
      new TemplateUpdatedEvent({
        before: {
          _id: fixturesFactory.id(extractedTemplateName),
          name: extractedTemplateName,
          properties: [
            fixturesFactory.property('not_extracted_property_1', propertyTypes.text),
            fixturesFactory.property('not_extracted_property_2', propertyTypes.numeric),
            fixturesFactory.property('extracted_property_1', propertyTypes.text),
            fixturesFactory.property('extracted_property_2', propertyTypes.numeric),
            fixturesFactory.property('select_property', propertyTypes.select),
            fixturesFactory.property('multiselect_property', propertyTypes.multiselect),
            fixturesFactory.property('relationship_property', propertyTypes.relationship),
          ],
        },
        after: {
          _id: fixturesFactory.id(extractedTemplateName),
          name: extractedTemplateName,
          properties: [
            fixturesFactory.property('not_extracted_property_1', propertyTypes.text),
            fixturesFactory.property('not_extracted_property_2', propertyTypes.numeric),
            fixturesFactory.property('extracted_property_1', propertyTypes.text),
            fixturesFactory.property('extracted_property_2_renamed', propertyTypes.numeric),
            fixturesFactory.property('select_property', propertyTypes.select),
            fixturesFactory.property('multiselect_property_renamed', propertyTypes.multiselect),
            fixturesFactory.property('relationship_property', propertyTypes.relationship),
          ],
        },
      })
    );

    const extractors = await testingDB.mongodb?.collection('ixextractors').find({}).toArray();

    expect(extractors).toEqual([
      fixturesFactory.ixExtractor('title_extractor', 'title', [
        extractedTemplateName,
        otherExtractedTemplateName,
      ]),
      fixturesFactory.ixExtractor('extractor1', 'extracted_property_1', [
        extractedTemplateName,
        otherExtractedTemplateName,
      ]),
      fixturesFactory.ixExtractor('extractor2', 'extracted_property_2', []),
      fixturesFactory.ixExtractor('extractor3', 'some_property', ['some_other_template']),
      fixturesFactory.ixExtractor('extractor4', 'extracted_property_2_1', [
        otherExtractedTemplateName,
      ]),
      fixturesFactory.ixExtractor('extractor5', 'extracted_property_2_2', [
        otherExtractedTemplateName,
      ]),
      fixturesFactory.ixExtractor('extractor6', 'select_property', [extractedTemplateName]),
      fixturesFactory.ixExtractor('extractor7', 'multiselect_property', []),
      fixturesFactory.ixExtractor('extractor8', 'relationship_property', [extractedTemplateName]),
    ]);

    const suggestions = await testingDB.mongodb?.collection('ixsuggestions').find({}).toArray();

    expect(suggestions).toMatchObject([
      {
        entityId: 'entity for new file',
        entityTemplate: fixturesFactory.id(extractedTemplateName).toString(),
        propertyName: 'extracted_property_1',
        extractorId: fixturesFactory.id('extractor1'),
      },
      {
        entityId: 'entity for new file',
        entityTemplate: fixturesFactory.id(extractedTemplateName).toString(),
        propertyName: 'title',
        extractorId: fixturesFactory.id('title_extractor'),
      },
      {
        entityId: 'entity for new file',
        entityTemplate: fixturesFactory.id(extractedTemplateName).toString(),
        propertyName: 'select_property',
        extractorId: fixturesFactory.id('extractor6'),
      },
      {
        entityId: 'entity for new file',
        entityTemplate: fixturesFactory.id(extractedTemplateName).toString(),
        propertyName: 'relationship_property',
        extractorId: fixturesFactory.id('extractor8'),
      },
    ]);
  });
});

describe(`On ${TemplateDeletedEvent.name}`, () => {
  it('should delete the template from the extractor if the property not longer exists', async () => {
    await applicationEventsBus.emit(
      new TemplateDeletedEvent({
        templateId: fixturesFactory.id(extractedTemplateName).toString(),
      })
    );

    const extractors = await testingDB.mongodb?.collection('ixextractors').find({}).toArray();

    expect(extractors).toEqual([
      fixturesFactory.ixExtractor('title_extractor', 'title', [otherExtractedTemplateName]),
      fixturesFactory.ixExtractor('extractor1', 'extracted_property_1', [
        otherExtractedTemplateName,
      ]),
      fixturesFactory.ixExtractor('extractor2', 'extracted_property_2', []),
      fixturesFactory.ixExtractor('extractor3', 'some_property', ['some_other_template']),
      fixturesFactory.ixExtractor('extractor4', 'extracted_property_2_1', [
        otherExtractedTemplateName,
      ]),
      fixturesFactory.ixExtractor('extractor5', 'extracted_property_2_2', [
        otherExtractedTemplateName,
      ]),
      fixturesFactory.ixExtractor('extractor6', 'select_property', []),
      fixturesFactory.ixExtractor('extractor7', 'multiselect_property', []),
      fixturesFactory.ixExtractor('extractor8', 'relationship_property', []),
    ]);
  });

  it('should delete the suggestions related to the template', async () => {
    await applicationEventsBus.emit(
      new TemplateDeletedEvent({
        templateId: fixturesFactory.id(extractedTemplateName).toString(),
      })
    );

    const suggestions = await testingDB.mongodb?.collection('ixsuggestions').find({}).toArray();

    expect(suggestions).toEqual([]);
  });

  it('should not act if the feature is not enabld', async () => {
    await disableFeatures();

    const suggestions = await testingDB.mongodb?.collection('ixsuggestions').find({}).toArray();
    const extractors = await testingDB.mongodb?.collection('ixextractors').find({}).toArray();

    await applicationEventsBus.emit(
      new TemplateDeletedEvent({
        templateId: fixturesFactory.id(extractedTemplateName).toString(),
      })
    );

    expect(suggestions).toEqual(
      await testingDB.mongodb?.collection('ixsuggestions').find({}).toArray()
    );
    expect(extractors).toEqual(
      await testingDB.mongodb?.collection('ixextractors').find({}).toArray()
    );
  });
});