---
id: TST-PFM-DJE-117
title: Contact data jobs
kind: integration
product_area: platform
module: data-job-engine
revision: 1
status: draft
source_sha:
  services-platform: ad3f6a6582843008ed0f5a17b131878d15829af1
  macroservice-libraries: 54536a441935c6e09fdb7f96f0bec71c52ba4e03
  services-crm: a8d1364f1dd44b723bb5f9acba12f983e9ec8d69
  services-membership: 5994c270e8a37a30f848e99ba5b5ee6b87c08893
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
repository: services-crm
files:
  - modules/data-jobs/tests/integration/services/jobs/contact-data-jobs.integration.spec.js
implements:
  - ATS-PFM-DJE-001-03
  - ATS-PFM-DJE-001-04
  - ATS-PFM-DJE-001-05
  - ATS-PFM-DJE-001-06
  - ATS-PFM-DJE-001-09
  - ATS-PFM-DJE-002-01
  - ATS-PFM-DJE-002-02
  - ATS-PFM-DJE-003-01
  - ATS-PFM-DJE-003-02
  - ATS-PFM-DJE-003-03
  - ATS-PFM-DJE-003-04
  - ATS-PFM-DJE-003-10
  - ATS-PFM-DJE-004-02
  - ATS-PFM-DJE-005-03
  - ATS-PFM-DJE-005-04
environment: local
implementation_state: implemented
latest_result: not-run
latest_run_at: null
latest_run_ref: null
conformance: not-verified
estimated_minutes: 5
isolation: >-
  Suite-owned fixture records, job identifiers and workspace contexts; teardown removes the fixture
  data.
evidence:
  - >-
    services-crm:modules/data-jobs/tests/integration/services/jobs/contact-data-jobs.integration.spec.js
assertion_inventory:
  - 'a bulk contact update: the submission'
  - accepts the first submission as a job of its own
  - 'answers the second submission with the first job, having written nothing'
  - 'holds one submission row for the key, so the ledger is what answered the second'
  - 'freezes one item per id, in the order the request named them'
  - >-
    freezes the patch on every item, so a later apply writes what was submitted and not what is
    asked for then
  - queues a batch for every batch it cut the job into
  - 'refuses a field the bulk update may not set, naming it, before any contact is touched'
  - 'refuses an object type CRM performs no bulk operation on, naming what it does perform'
  - 'a bulk contact update: what each contact’s outcome is'
  - applies the 95 contacts nothing refused
  - says of a job of records named by id that it has no source and runs under no conflict policy
  - 'refuses the other five, counting each as what refused it'
  - writes the patch to a contact it applied
  - leaves a refused contact exactly as it was
  - leaves the locked contact exactly as it was
  - 'says of each refusal what was wrong with it, by code'
  - 'names the rule a refused value broke, and quotes no value of the record'
  - 'writes a receipt for every contact it changed, so a redelivery knows what it already did'
  - 'finishes the job as completed with rejections, which is what a job with both outcomes is'
  - 'counts every batch it planned as finished, which is what lets a client say “batch 7 of 20”'
  - 'answers a page of results in item order, each with its outcome'
  - 'answers the next page from the cursor the last one gave, so no item is read twice'
  - 'answers only the items of the outcome asked for, which is how a client lists what was applied'
  - 'pages the rejections, so a client can correct them without reading the whole job'
  - applies nothing a second time when the queue delivers a committed batch again
  - 'a bulk contact update: what every change it makes says about itself'
  - writes a revision for the change the job made
  - 'records a job as the source of that revision, not a console'
  - attributes the revision to the person who submitted the job
  - 'stamps the job on the revision, so every record one job changed can be listed'
  - 'stamps the batch on it too, so the transaction that made the change is named'
  - 'carries the job on the change event, so a handler knows the change was a job’s before it reacts'
  - 'a bulk contact update: a patch that repeats what the contact already holds'
  - 'answers the item applied and unchanged, at the version the contact still stands at'
  - leaves the contact’s version where the first job left it
  - 'writes no revision, so the history names only the job that changed the contact'
  - queues no change event for the job that changed nothing
  - 'a bulk contact update: validating first, then applying'
  - changes nothing while it is validating
  - 'reports what the validation would have done, so the submitter can decide to apply it'
  - 'leaves the validated job waiting to be applied, not finished'
  - 'answers an impact digest for the validated update, which the apply echoes (premise)'
  - refuses an apply of the validated update that echoes no impact digest
  - refuses an apply of the validated update whose digest is not the one the validation answered
  - 'leaves the job validated after both refusals, so the refused applies queued nothing'
  - refuses the contact that changed between the validation and the apply as a conflict
  - applies the contact nobody touched
  - leaves the contact somebody did touch as that person left it
  - counts the one conflict and the one applied record on the job
  - 'a bulk contact update: stopping one'
  - runs no batch of a paused job
  - leaves the paused job’s contact untouched
  - runs no batch of a cancelled job either
  - leaves the cancelled job cancelled
  - 'reports the cancelled job’s item, which no batch reached, as not attempted (premise)'
  - >-
    lists that item under outcome=not_attempted, which is how a client lists what a stopped job
    never got to
  - lists it under no other outcome
  - 'a bulk contact update: cancelled while a batch is running'
  - >-
    answers the cancel as waiting for the one batch in flight, with the other batch’s five contacts
    not attempted (premise)
  - finishes the batch that was running and runs none of the other
  - 'ends the job cancelled, not failed, once the batch it waited for has finished'
  - reports what the running batch committed and what the cancel kept from running
  - 'a bulk contact update: the tenant it is confined to'
  - answers the job to its own tenant
  - 'tells another tenant the job does not exist, rather than anything about it'
  - 'refuses to run its batch for another tenant, which reaches no job of that name at all'
  - 'keeps the job row in its own tenant, which is the row the refusals above are about'
  - 'a bulk contact update: the contacts the submitter may reach'
  - changes the contact the person may reach
  - leaves the contact outside their grants exactly as it was
  - >-
    rejects that contact as not found, which tells the person nothing about a record they may not
    see
  - counts the one it applied and the one it refused
registration_note: Registration describes existing source; the suite is not executed by this documentation job.
---

# Contact data jobs

## Verification Boundary

The suite checks the production service or shared engine against its database fixture. The structured assertion inventory records the checks present at the pinned revision.

## What This Suite Proves

The existing checks exercise contact data jobs. The specification bindings identify the narrower outcomes supported by those assertions.

## What It Does Not Prove

Local execution does not establish deployed queue, storage or service behavior. Independent test cases do not establish a complete cross-feature process.

## Real Dependencies and Test Doubles

The owning service exercises production persistence and domain behavior against real Postgres. The fixture supplies caller contexts; queue delivery, storage and external calls are doubled where the file installs a test port.

## Must Not Be Doubled

The database effects, security context and production operation under examination remain real. Their outcomes cannot be replaced with canned success responses.
