<commit_before>import { Substitute as Sub, Arg, SubstituteOf } from '@fluffy-spoon/substitute'
import { expect } from 'chai'
import uniqid from 'uniqid'
import * as api from '../../../lib/app.api/observations/app.api.observations'
import { AllocateObservationId, SaveObservation } from '../../../lib/app.impl/observations/app.impl.observations'
import { copyMageEventAttrs, MageEvent } from '../../../lib/entities/events/entities.events'
import { addAttachment, Attachment, AttachmentCreateAttrs, copyAttachmentAttrs, copyObservationAttrs, EventScopedObservationRepository, Observation, ObservationAttrs, ObservationRepositoryError, ObservationRepositoryErrorCode, removeAttachment } from '../../../lib/entities/observations/entities.observations'
import { permissionDenied, MageError, ErrPermissionDenied, ErrEntityNotFound, EntityNotFoundError, InvalidInputError, ErrInvalidInput, PermissionDeniedError } from '../../../lib/app.api/app.api.errors'
import { FormFieldType } from '../../../lib/entities/events/entities.events.forms'
import _ from 'lodash'

describe.only('observations use case interactions', function() {

  let mageEvent: MageEvent
  let obsRepo: SubstituteOf<EventScopedObservationRepository>
  let permissions: SubstituteOf<api.ObservationPermissionService>
  let context: api.ObservationRequestContext
  let principalHandle: SubstituteOf<{ requestingPrincipal(): string }>

  beforeEach(function() {

    mageEvent = new MageEvent({
      id: Date.now(),
      acl: {},
      feedIds: [],
      forms: [],
      layerIds: [],
      name: 'Observation App Layer Tests',
      style: {}
    })
    obsRepo = Sub.for<EventScopedObservationRepository>()
    permissions = Sub.for<api.ObservationPermissionService>()
    principalHandle = Sub.for<{ requestingPrincipal(): string }>()
    context = {
      mageEvent,
      userId: uniqid(),
      deviceId: uniqid(),
      observationRepository: obsRepo,
      requestToken: uniqid(),
      requestingPrincipal() { return principalHandle.requestingPrincipal() },
      locale() { return null }
    }
  })

  describe('allocating observation ids', function() {

    let allocateObservationId: api.AllocateObservationId

    beforeEach(function() {
      allocateObservationId = AllocateObservationId(permissions)
    })

    it('fails without permission', async function() {

      permissions.ensureCreateObservationPermission(Arg.all()).resolves(permissionDenied('create observation', 'test1'))
      const res = await allocateObservationId({ context })

      expect(res.success).to.be.null
      expect(res.error).to.be.instanceOf(MageError)
      expect(res.error?.code).to.equal(ErrPermissionDenied)
      permissions.received(1).ensureCreateObservationPermission(context)
      obsRepo.didNotReceive().allocateObservationId()
    })

    it('gets a new observation id from the context repository', async function() {

      const id = uniqid()
      permissions.ensureCreateObservationPermission(Arg.all()).resolves(null)
      obsRepo.allocateObservationId().resolves(id)
      const res = await allocateObservationId({ context })

      expect(res.error).to.be.null
      expect(res.success).to.equal(id)
    })

    it.skip('TODO: handles rejected promises')
  })

  describe('saving observations', function() {

    let saveObservation: api.SaveObservation
    let minimalObs: ObservationAttrs

    beforeEach(function() {
      saveObservation = SaveObservation(permissions)
      minimalObs = {
        id: uniqid(),
        eventId: mageEvent.id,
        createdAt: new Date(),
        lastModified: new Date(),
        type: 'Feature',
        geometry: { type: 'Point', coordinates: [ 13, 57 ] },
        properties: {
          timestamp: new Date(),
          forms: []
        },
        attachments: [],
        states: [],
      }
      permissions.ensureCreateObservationPermission(Arg.all()).resolves(null)
      permissions.ensureUpdateObservationPermission(Arg.all()).resolves(null)
    })

    it('does not save when the obsevation event id does not match the context event', async function() {

      const eventIdOverride = mageEvent.id * 3
      const req: api.SaveObservationRequest = {
        context,
        observation: { ...observationModFor(minimalObs), eventId: eventIdOverride } as any
      }
      obsRepo.findById(Arg.any()).resolves(null)
      obsRepo.save(Arg.any()).resolves(new ObservationRepositoryError(ObservationRepositoryErrorCode.InvalidObservation))
      const res = await saveObservation(req)
      const err = res.error as InvalidInputError

      expect(res.success).to.be.null
      expect(err.code).to.equal(ErrInvalidInput)
      obsRepo.received(1).save(Arg.any())
    })

    describe('creating', function() {

      beforeEach(function() {
        obsRepo.findById(Arg.any()).resolves(null)
      })

      it('ensures create permission when no observation exists', async function() {

        const deny = Sub.for<api.ObservationPermissionService>()
        deny.ensureCreateObservationPermission(Arg.all()).resolves(permissionDenied('test create', context.userId, minimalObs.id))
        saveObservation = SaveObservation(deny)
        const req: api.SaveObservationRequest = {
          context,
          observation: observationModFor(minimalObs)
        }
        const res = await saveObservation(req)
        const denied = res.error as PermissionDeniedError

        expect(res.success).to.be.null
        expect(denied).to.be.instanceOf(MageError)
        expect(denied.code).to.equal(ErrPermissionDenied)
        deny.received(1).ensureCreateObservationPermission(context)
        deny.didNotReceive().ensureUpdateObservationPermission(Arg.all())
        obsRepo.didNotReceive().save(Arg.all())
      })

      it('validates the id for a new observation', async function() {

        const req: api.SaveObservationRequest = {
          context,
          observation: observationModFor(minimalObs)
        }
        obsRepo.save(Arg.any()).resolves(new ObservationRepositoryError(ObservationRepositoryErrorCode.InvalidObservationId))
        const res = await saveObservation(req)
        const err = res.error as EntityNotFoundError

        expect(res.success).to.be.null
        expect(err).to.be.instanceOf(MageError)
        expect(err.code).to.equal(ErrEntityNotFound)
        expect(err.data.entityId).to.equal(minimalObs.id)
        expect(err.data.entityType).to.equal('ObservationId')
        obsRepo.received(1).save(Arg.any())
      })

      it('populates creating user id and device id fron request context', async function() {
        expect.fail('todo')
      })
    })

    describe('updating', function() {
<commit_msg>[service] wip: save observation app layer: ensure created observations get context user id and device id<commit_after>import { Substitute as Sub, Arg, SubstituteOf } from '@fluffy-spoon/substitute'
import { expect } from 'chai'
import uniqid from 'uniqid'
import * as api from '../../../lib/app.api/observations/app.api.observations'
import { AllocateObservationId, SaveObservation } from '../../../lib/app.impl/observations/app.impl.observations'
import { copyMageEventAttrs, MageEvent } from '../../../lib/entities/events/entities.events'
import { addAttachment, Attachment, AttachmentCreateAttrs, copyAttachmentAttrs, copyObservationAttrs, EventScopedObservationRepository, Observation, ObservationAttrs, ObservationRepositoryError, ObservationRepositoryErrorCode, removeAttachment } from '../../../lib/entities/observations/entities.observations'
import { permissionDenied, MageError, ErrPermissionDenied, ErrEntityNotFound, EntityNotFoundError, InvalidInputError, ErrInvalidInput, PermissionDeniedError } from '../../../lib/app.api/app.api.errors'
import { FormFieldType } from '../../../lib/entities/events/entities.events.forms'
import _ from 'lodash'

describe.only('observations use case interactions', function() {

  let mageEvent: MageEvent
  let obsRepo: SubstituteOf<EventScopedObservationRepository>
  let permissions: SubstituteOf<api.ObservationPermissionService>
  let context: api.ObservationRequestContext
  let principalHandle: SubstituteOf<{ requestingPrincipal(): string }>

  beforeEach(function() {

    mageEvent = new MageEvent({
      id: Date.now(),
      acl: {},
      feedIds: [],
      forms: [],
      layerIds: [],
      name: 'Observation App Layer Tests',
      style: {}
    })
    obsRepo = Sub.for<EventScopedObservationRepository>()
    permissions = Sub.for<api.ObservationPermissionService>()
    principalHandle = Sub.for<{ requestingPrincipal(): string }>()
    context = {
      mageEvent,
      userId: uniqid(),
      deviceId: uniqid(),
      observationRepository: obsRepo,
      requestToken: uniqid(),
      requestingPrincipal() { return principalHandle.requestingPrincipal() },
      locale() { return null }
    }
  })

  describe('allocating observation ids', function() {

    let allocateObservationId: api.AllocateObservationId

    beforeEach(function() {
      allocateObservationId = AllocateObservationId(permissions)
    })

    it('fails without permission', async function() {

      permissions.ensureCreateObservationPermission(Arg.all()).resolves(permissionDenied('create observation', 'test1'))
      const res = await allocateObservationId({ context })

      expect(res.success).to.be.null
      expect(res.error).to.be.instanceOf(MageError)
      expect(res.error?.code).to.equal(ErrPermissionDenied)
      permissions.received(1).ensureCreateObservationPermission(context)
      obsRepo.didNotReceive().allocateObservationId()
    })

    it('gets a new observation id from the context repository', async function() {

      const id = uniqid()
      permissions.ensureCreateObservationPermission(Arg.all()).resolves(null)
      obsRepo.allocateObservationId().resolves(id)
      const res = await allocateObservationId({ context })

      expect(res.error).to.be.null
      expect(res.success).to.equal(id)
    })

    it.skip('TODO: handles rejected promises')
  })

  describe('saving observations', function() {

    let saveObservation: api.SaveObservation
    let minimalObs: ObservationAttrs

    beforeEach(function() {
      saveObservation = SaveObservation(permissions)
      minimalObs = {
        id: uniqid(),
        eventId: mageEvent.id,
        createdAt: new Date(),
        lastModified: new Date(),
        type: 'Feature',
        geometry: { type: 'Point', coordinates: [ 13, 57 ] },
        properties: {
          timestamp: new Date(),
          forms: []
        },
        attachments: [],
        states: [],
      }
      permissions.ensureCreateObservationPermission(Arg.all()).resolves(null)
      permissions.ensureUpdateObservationPermission(Arg.all()).resolves(null)
    })

    it('does not save when the obsevation event id does not match the context event', async function() {

      const eventIdOverride = mageEvent.id * 3
      const req: api.SaveObservationRequest = {
        context,
        observation: { ...observationModFor(minimalObs), eventId: eventIdOverride } as any
      }
      obsRepo.findById(Arg.any()).resolves(null)
      obsRepo.save(Arg.any()).resolves(new ObservationRepositoryError(ObservationRepositoryErrorCode.InvalidObservation))
      const res = await saveObservation(req)
      const err = res.error as InvalidInputError

      expect(res.success).to.be.null
      expect(err.code).to.equal(ErrInvalidInput)
      obsRepo.received(1).save(Arg.any())
    })

    describe('creating', function() {

      beforeEach(function() {
        obsRepo.findById(Arg.any()).resolves(null)
      })

      it('ensures create permission when no observation exists', async function() {

        const deny = Sub.for<api.ObservationPermissionService>()
        deny.ensureCreateObservationPermission(Arg.all()).resolves(permissionDenied('test create', context.userId, minimalObs.id))
        saveObservation = SaveObservation(deny)
        const req: api.SaveObservationRequest = {
          context,
          observation: observationModFor(minimalObs)
        }
        const res = await saveObservation(req)
        const denied = res.error as PermissionDeniedError

        expect(res.success).to.be.null
        expect(denied).to.be.instanceOf(MageError)
        expect(denied.code).to.equal(ErrPermissionDenied)
        deny.received(1).ensureCreateObservationPermission(context)
        deny.didNotReceive().ensureUpdateObservationPermission(Arg.all())
        obsRepo.didNotReceive().save(Arg.all())
      })

      it('validates the id for a new observation', async function() {

        const req: api.SaveObservationRequest = {
          context,
          observation: observationModFor(minimalObs)
        }
        obsRepo.save(Arg.any()).resolves(new ObservationRepositoryError(ObservationRepositoryErrorCode.InvalidObservationId))
        const res = await saveObservation(req)
        const err = res.error as EntityNotFoundError

        expect(res.success).to.be.null
        expect(err).to.be.instanceOf(MageError)
        expect(err.code).to.equal(ErrEntityNotFound)
        expect(err.data.entityId).to.equal(minimalObs.id)
        expect(err.data.entityType).to.equal('ObservationId')
        obsRepo.received(1).save(Arg.any())
      })

      it('populates creating user id and device id fron request context', async function() {

        const req: api.SaveObservationRequest = {
          context,
          observation: observationModFor(minimalObs)
        }
        const createdAttrs: ObservationAttrs = {
          ...copyObservationAttrs(minimalObs),
          userId: context.userId,
          deviceId: context.deviceId
        }
        const created = Observation.evaluate(createdAttrs, mageEvent)
        obsRepo.save(Arg.all()).resolves(created)
        const res = await saveObservation(req)
        const saved = res.success as api.ExoObservation

        expect(res.error).to.be.null
        expect(created.validation.hasErrors).to.be.false
        expect(created.userId).to.equal(context.userId)
        expect(created.deviceId).to.equal(context.deviceId)
        expect(saved).to.deep.equal(api.exoObservationFor(created))
        obsRepo.received(1).save(Arg.all())
        obsRepo.received(1).save(Arg.is(equalToObservationIgnoringDates(created)))
      })
    })

    describe('updating', function() {

{'fraction_non_alphanumeric': 0.11702424073558094, 'fraction_numerical': 0.0015324602953468932, 'mean_word_length': 3.4270736971939564, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1367269', 'n_tokens_mistral': 4019, 'n_tokens_neox': 3948, 'n_words': 759}