From 4a764cb4d26e1e97d209c15c445b3f87021e5a84 Mon Sep 17 00:00:00 2001 From: rozetko Date: Thu, 15 Aug 2019 21:25:21 +0300 Subject: [PATCH] Tests for detection-spans (#747) --- server/spec/models/detection_model.jest.ts | 75 +++++++++++++++++++ .../spec/utils_for_tests/detection_spans.ts | 28 +++++++ server/src/models/detection_model.ts | 11 +++ server/src/utils/spans.ts | 1 + 4 files changed, 115 insertions(+) create mode 100644 server/spec/models/detection_model.jest.ts create mode 100644 server/spec/utils_for_tests/detection_spans.ts diff --git a/server/spec/models/detection_model.jest.ts b/server/spec/models/detection_model.jest.ts new file mode 100644 index 0000000..e660696 --- /dev/null +++ b/server/spec/models/detection_model.jest.ts @@ -0,0 +1,75 @@ +import { TEST_ANALYTIC_UNIT_ID } from '../utils_for_tests/analytic_units'; +import { buildSpans, insertSpans, clearSpansDB, convertSpansToOptions } from '../utils_for_tests/detection_spans'; + +import * as Detection from '../../src/models/detection_model'; + +import * as _ from 'lodash'; + +const INITIAL_SPANS_CONFIGS = [ + { from: 1, to: 3, status: Detection.DetectionStatus.READY }, + { from: 4, to: 5, status: Detection.DetectionStatus.RUNNING } +]; + +beforeEach(async () => { + await insertSpans(INITIAL_SPANS_CONFIGS); +}); + +afterEach(clearSpansDB); + +describe('insertSpan', () => { + it('should merge spans with the same status', async () => { + await insertSpans([ + { from: 3, to: 5, status: Detection.DetectionStatus.READY } + ]); + + const expectedSpans = [ + { from: 1, to: 5, status: Detection.DetectionStatus.READY } + ]; + const spansInDB = await Detection.findMany(TEST_ANALYTIC_UNIT_ID, { }); + const spansOptions = convertSpansToOptions(spansInDB); + expect(spansOptions).toEqual(expectedSpans); + }); + + + it('should merge spans if existing span is inside the one being inserted', async () => { + await insertSpans([ + { from: 1, to: 6, status: Detection.DetectionStatus.RUNNING } + ]); + + const expectedSpans = [ + { from: 1, to: 6, status: Detection.DetectionStatus.RUNNING } + ]; + const spansInDB = await Detection.findMany(TEST_ANALYTIC_UNIT_ID, {}); + const spansOptions = convertSpansToOptions(spansInDB); + expect(spansOptions).toEqual(expectedSpans); + }); +}); + +describe('getIntersectedSpans', () => { + it('should find all intersections with the inserted span', async () => { + const testCases = [ + { + from: 1, to: 5, + expected: [ + { from: 1, to: 3, status: Detection.DetectionStatus.READY }, + { from: 3, to: 4, status: Detection.DetectionStatus.RUNNING } + ] + }, + { from: 4, to: 5, expected: [{ from: 3, to: 4, status: Detection.DetectionStatus.RUNNING }] }, + { from: 6, to: 7, expected: [] } + ] + + for(let testCase of testCases) { + const intersectedSpans = await Detection.getIntersectedSpans(TEST_ANALYTIC_UNIT_ID, testCase.from, testCase.to); + const intersectedSpansOptions = convertSpansToOptions(intersectedSpans); + expect(intersectedSpansOptions).toEqual(testCase.expected); + } + }); +}); + +describe('getSpanBorders', () => { + it('should sort and find span borders', () => { + const borders = Detection.getSpanBorders(buildSpans(INITIAL_SPANS_CONFIGS)); + expect(borders).toEqual([1, 3, 3, 4]); + }); +}); diff --git a/server/spec/utils_for_tests/detection_spans.ts b/server/spec/utils_for_tests/detection_spans.ts new file mode 100644 index 0000000..a65b462 --- /dev/null +++ b/server/spec/utils_for_tests/detection_spans.ts @@ -0,0 +1,28 @@ +import { TEST_ANALYTIC_UNIT_ID } from './analytic_units'; + +import * as Detection from '../../src/models/detection_model'; + +import * as _ from 'lodash'; + +export type DetectionSpanOptions = { from: number, to: number, status: Detection.DetectionStatus }; + +export function buildSpans(options: DetectionSpanOptions[]): Detection.DetectionSpan[] { + return options.map(option => { + return new Detection.DetectionSpan(TEST_ANALYTIC_UNIT_ID, option.from, option.to, option.status); + }); +} + +export async function insertSpans(options: DetectionSpanOptions[]): Promise { + const spansToInsert = buildSpans(options); + const insertPromises = spansToInsert.map(async span => Detection.insertSpan(span)); + await Promise.all(insertPromises); +} + +export function convertSpansToOptions(spans: Detection.DetectionSpan[]): DetectionSpanOptions[] { + const spansOptions = spans.map(span => ({ from: span.from, to: span.to, status: span.status })); + return _.sortBy(spansOptions, spanOptions => spanOptions.from); +} + +export async function clearSpansDB(): Promise { + await Detection.clearSpans(TEST_ANALYTIC_UNIT_ID); +} diff --git a/server/src/models/detection_model.ts b/server/src/models/detection_model.ts index 2d1948a..cf1d592 100644 --- a/server/src/models/detection_model.ts +++ b/server/src/models/detection_model.ts @@ -2,6 +2,7 @@ import { AnalyticUnitId } from './analytic_units'; import { Collection, makeDBQ } from '../services/data_service'; import * as _ from 'lodash'; +import { getNonIntersectedSpans } from '../utils/spans'; let db = makeDBQ(Collection.DETECTION_SPANS); @@ -13,6 +14,12 @@ export enum DetectionStatus { export type DetectionId = string; +/** + * Detection-span represents the state of dataset segment: + * - READY: detection is done + * - RUNNING: detection is running + * - FAILED: detection failed + */ export class DetectionSpan { constructor( public analyticUnitId: AnalyticUnitId, @@ -130,6 +137,10 @@ export async function insertSpan(span: DetectionSpan) { return db.insertOne(spanToInsert); } +/** + * Sorts spans by `from` field and @returns an array of their borders + */ +// TODO: remove after getNonIntersectedSpans refactoring export function getSpanBorders(spans: DetectionSpan[]): number[] { let spanBorders: number[] = []; diff --git a/server/src/utils/spans.ts b/server/src/utils/spans.ts index 606f1fe..7707bdf 100644 --- a/server/src/utils/spans.ts +++ b/server/src/utils/spans.ts @@ -7,6 +7,7 @@ export declare type Span = { to: number } +// TODO: move from utils and refactor export function getNonIntersectedSpans(from: number, to: number, spanBorders: number[]): Span[] { // spanBorders array must be sorted ascending let isFromProcessed = false;