예제 #1
0
    def test_09_remove_permanent(self):
        # Separate an incoming Request from its corresponding PublicAPC, leaving no owners, thus deleting the record

        source = RequestFixtureFactory.example()
        req = Request(source)
        req.owner = "test"

        # create a record with 2 distinct apcs from different owners
        source2 = PublicAPCFixtureFactory.example()
        pub = PublicAPC(source2)
        pub.remove_apcs_by_owner("abcdefg")  # clear the existing apc record

        apc_record = PublicAPCFixtureFactory.apc_record()
        del apc_record["ref"]  # do this so that the ref gets created correctly later
        pub.add_apc_for_owner("test", apc_record)  # add a new, known one

        pub.save(blocking=True)

        # now request the removal
        PublicApi.remove(req)
        time.sleep(2)

        dao = PublicAPC()
        pub2 = dao.pull(pub.id)
        assert pub2 is None
예제 #2
0
    def test_15_public_indexing(self):
        # Check that public records are indexed correctly

        source = PublicAPCFixtureFactory.example()
        pub = PublicAPC(source)

        apc_record1 = PublicAPCFixtureFactory.apc_record()
        apc_record2 = PublicAPCFixtureFactory.apc_record()

        del apc_record1["amount_inc_vat_gbp"]
        apc_record1["amount_ex_vat_gbp"] = 1000
        apc_record1["vat_gbp"] = 200
        apc_record1["additional_costs"] = 100

        apc_record2["amount_inc_vat_gbp"] = 2400
        apc_record2["amount_ex_vat_gbp"] = 2000
        apc_record2["vat_gbp"] = 400
        apc_record2["additional_costs"] = 200

        pub.apc_records = [apc_record1, apc_record2]
        pub.prep()

        # first check that the amount_inc_vat_gbp was calculated or kept
        assert pub.apc_records[0]["amount_inc_vat_gbp"] == 1200

        # now check all the indexed amounts add up
        assert pub.data.get("index", {}).get("additional_costs") == 300
        assert pub.data.get("index", {}).get("vat") == 600
        assert pub.data.get("index", {}).get("amount_ex_vat") == 3000
        assert pub.data.get("index", {}).get("amount_inc_vat") == 3600
        assert pub.data.get("index", {}).get("grand_total") == 3900
예제 #3
0
    def test_06_publish_update(self):
        # Publish an update to an existing PublicAPC

        merge_source = PublicAPCFixtureFactory.record_merge_source()
        merge_target = PublicAPCFixtureFactory.record_merge_target()
        apc_record = PublicAPCFixtureFactory.apc_record()
        result = PublicAPCFixtureFactory.record_merge_result()

        del merge_source["jm:apc"]
        del merge_target["jm:apc"]
        del result["jm:apc"]

        first = deepcopy(apc_record)
        second = deepcopy(apc_record)
        third = deepcopy(apc_record)

        first["organisation_name"] = "First"
        del first["ref"]
        second["organisation_name"] = "Second"
        del second["ref"]
        third["organisation_name"] = "Third"
        del third["ref"]

        req = Request()
        req.record = merge_source
        req.add_apc_record(first)
        req.owner = "11111"

        pub = PublicAPC()
        pub.record = merge_target
        pub.add_apc_for_owner("22222", second)
        pub.add_apc_for_owner("11111", third)
        pub.save(blocking=True)

        PublicApi.publish(req)

        dao = PublicAPC()
        pub2 = dao.pull(pub.id)

        # first check that the apcs are as we would expect
        one = pub2.get_apcs_by_owner("11111")
        two = pub2.get_apcs_by_owner("22222")

        assert len(one) == 1
        assert len(two) == 1
        assert one[0]["organisation_name"] == "First"
        assert two[0]["organisation_name"] == "Second"

        # now check that the metadata merge proceeded correctly
        record = pub2.record
        del record["jm:apc"]
        assert record == result
예제 #4
0
    def test_04_pull_public(self):
        # Pull a PublicAPC through the ApiRequest object

        acc = MonitorUKAccount()
        acc.id = "abcdefghij"
        acc.save()

        acc2 = MonitorUKAccount()
        acc2.id = "qwerty"
        acc.save(blocking=True)

        # make a competing public version, and check that a pull on the doi works
        pub_source = PublicAPCFixtureFactory.example()
        pub = PublicAPC(pub_source)
        pub.save(blocking=True)

        result = ApiRequest.pull("10.1234/me", account=acc)
        assert result.raw == pub.clean_record
        assert result.account.id == acc.id
        assert result.public_id == pub.id

        craw = deepcopy(pub.clean_record)
        craw["@context"] = app.config.get("API_JSON_LD_CONTEXT")
        assert json.loads(result.json()) == craw

        # also try the pull with the wrong owner, which should work
        result = ApiRequest.pull("10.1234/me", account=acc2)
        assert result is not None
예제 #5
0
    def test_03_public(self):
        # Check we can instantiate and work with a PublicAPC

        # first make a blank one
        pub = PublicAPC()

        # now make one around the fixture
        source = PublicAPCFixtureFactory.example()
        pub = PublicAPC(source)

        # make one with a broken source
        broken = {"whatever" : "broken"}
        with self.assertRaises(dataobj.DataStructureException):
            pub = PublicAPC(broken)

        # now make one bit by bit
        pub = PublicAPC()
        pub.record = source.get("record")
        pub.set_apc_ref("test1", "1111111111")

        apc_refs =  pub.get_apc_refs("test1")
        assert len(apc_refs) == 1
        assert apc_refs[0] == "1111111111"

        # now make it broken
        pub = PublicAPC()
        with self.assertRaises(dataobj.DataStructureException):
            pub.record = {"random" : "stuff"}
예제 #6
0
    def test_02_find_public_record(self):
        # Find a public record with a variety of identifiers

        source = PublicAPCFixtureFactory.example()
        pub = PublicAPC(source)
        pub.save(blocking=True)

        # document to form the basis of the queries
        source2 = RequestFixtureFactory.example()

        # create sources with one of each kind of identifier, then look them up using the
        # find_public_record and find_public_record_by_identifier methods
        pid = deepcopy(source2)
        del pid["record"]["dc:identifier"]
        req = Request(pid)
        req.public_id = pub.id
        pub1 = PublicApi.find_public_record(req)
        assert pub1 is not None

        doi = deepcopy(source2)
        doi["record"]["dc:identifier"] = [{"type": "doi", "id": "10.1234/me"}]
        req = Request(doi)
        pub1 = PublicApi.find_public_record(req)
        assert pub1 is not None
        pub11 = PublicApi.find_public_record_by_identifier("doi", "10.1234/me")
        assert pub11 is not None

        pmid = deepcopy(source2)
        pmid["record"]["dc:identifier"] = [{"type": "pmid", "id": "87654321"}]
        req = Request(pmid)
        pub1 = PublicApi.find_public_record(req)
        assert pub1 is not None
        pub11 = PublicApi.find_public_record_by_identifier("pmid", "87654321")
        assert pub11 is not None

        pmcid = deepcopy(source2)
        pmcid["record"]["dc:identifier"] = [{"type": "pmcid", "id": "PMC1234"}]
        req = Request(pmcid)
        pub1 = PublicApi.find_public_record(req)
        assert pub1 is not None
        pub11 = PublicApi.find_public_record_by_identifier("pmcid", "PMC1234")
        assert pub11 is not None

        url = deepcopy(source2)
        url["record"]["dc:identifier"] = [{"type": "url", "id": "http://example.com/whatever"}]
        req = Request(url)
        pub1 = PublicApi.find_public_record(req)
        assert pub1 is not None
        pub11 = PublicApi.find_public_record_by_identifier("url", "http://example.com/whatever")
        assert pub11 is not None

        # finally, ensure that you don't get a match when you shouldn't
        null = deepcopy(source2)
        null["record"]["dc:identifier"] = [{"type": "doi", "id": "10.1234/another"}]
        req = Request(null)
        pub1 = PublicApi.find_public_record(req)
        assert pub1 is None
        pub11 = PublicApi.find_public_record_by_identifier("doi", "10.1234/another")
        assert pub11 is None
예제 #7
0
    def test_11_public_refs(self):
        # Check that APC refs are handled correctly by PublicAPCs

        source = PublicAPCFixtureFactory.example()
        pub = PublicAPC(source)

        assert pub.record.get("jm:apc")[0]["ref"] == "1111111111"
        assert "ref" not in pub.clean_record.get("jm:apc")[0]
예제 #8
0
    def test_08_public_apc_methods(self):
        # Check all the PublcAPC object methods

        pub = PublicAPC()

        assert len(pub.get_apc_refs("11111")) == 0

        pub.set_apc_ref("11111", "aaaaa")
        assert pub.get_apc_refs("11111")[0] == "aaaaa"

        pub.set_apc_ref("22222", "bbbbb")
        assert len(pub.get_apc_refs("22222")) == 1

        pub.remove_apc_refs("11111")
        assert len(pub.get_apc_refs("22222")) == 1
        assert len(pub.get_apc_refs("11111")) == 0

        assert "11111" not in pub.list_owners()
        assert "22222" in pub.list_owners()

        pub.remove_apc_refs("22222")

        assert len(pub.get_apcs_by_owner("11111")) == 0

        apc_record = PublicAPCFixtureFactory.apc_record()

        first = deepcopy(apc_record)
        first["ref"] = "aaaaa"
        pub.add_apc_for_owner("11111", first)

        assert len(pub.get_apc_refs("11111")) == 1
        assert pub.get_apc_refs("11111")[0] == "aaaaa"
        assert len(pub.get_apcs_by_owner("11111")) == 1
        assert pub.get_apcs_by_owner("11111")[0]["ref"] == "aaaaa"

        second = deepcopy(apc_record)
        second["ref"] = "bbbbb"
        pub.add_apc_for_owner("22222", second)

        assert len(pub.get_apc_refs("22222")) == 1
        assert pub.get_apc_refs("22222")[0] == "bbbbb"
        assert len(pub.get_apcs_by_owner("22222")) == 1
        assert pub.get_apcs_by_owner("22222")[0]["ref"] == "bbbbb"

        assert len(pub.apc_records) == 2

        pub.remove_apcs_by_owner("11111")
        assert len(pub.apc_records) == 1
        assert pub.apc_records[0]["ref"] == "bbbbb"
        assert len(pub.get_apc_refs("22222")) == 1
        assert pub.get_apc_refs("22222")[0] == "bbbbb"
        assert len(pub.get_apcs_by_owner("22222")) == 1
        assert pub.get_apcs_by_owner("22222")[0]["ref"] == "bbbbb"

        pub.remove_apcs_by_owner("22222")
        assert len(pub.apc_records) == 0
        assert len(pub.get_apc_refs("11111")) == 0
        assert len(pub.get_apc_refs("22222")) == 0
예제 #9
0
    def test_05_enhance_metadata(self):
        # Enhance the bibliographic metadata on a PublicAPC record

        merge_source = PublicAPCFixtureFactory.record_merge_source()
        merge_target = PublicAPCFixtureFactory.record_merge_target()
        result = PublicAPCFixtureFactory.record_merge_result()

        source = PublicAPC()
        source.record = merge_source
        source.set_apc_ref("22222", "bbbbb")

        target = PublicAPC()
        target.record = merge_target
        target.set_apc_ref("11111", "aaaaa")

        PublicApi.enhance_metadata(source, target)

        assert target.record == result
        assert target.get_apc_refs("11111") == ["aaaaa"]
        assert target.get_apc_refs("22222") == []
예제 #10
0
    def test_14_copy_overwrite(self):
        # Check the copy and overwrite cloning methods work

        source = PublicAPCFixtureFactory.example()
        pub = PublicAPC(source)

        pub2 = pub.copy()

        assert pub2.record == pub.record
        assert pub2.admin == pub.admin
        assert pub2.id == pub.id

        source3 = PublicAPCFixtureFactory.example()
        source3["record"]["dc:title"] = "Overwrite"
        pub3 = PublicAPC(source3)

        pub2.overwrite(pub3)

        assert pub2.record == pub3.record
        assert pub2.admin == pub3.admin
        assert pub2.id == pub3.id
예제 #11
0
    def test_05_low_quota(self):
        # Check what happens when the use has a low quota on Lantern

        global QUOTA
        QUOTA = 1

        acc2 = MonitorUKAccount()
        acc2.email = "*****@*****.**"
        acc2.lantern_email = "*****@*****.**"
        acc2.lantern_api_key = "123456789"
        acc2.save()

        # a record that needs lantern because of a missing field
        source = PublicAPCFixtureFactory.make_record(acc2.id, None, None, None)
        del source["admin"]["lantern_lookup"]
        del source["record"]["rioxxterms:publication_date"]
        pub = PublicAPC(source)
        pub.save()

        # a record that needs lantern because it has timed out and has a missing field
        source = PublicAPCFixtureFactory.make_record(acc2.id, None, None, None)
        source["admin"]["lantern_lookup"] = dates.format(dates.before_now(31104000))   # a year ago
        del source["record"]["rioxxterms:publication_date"]
        pub = PublicAPC(source)
        pub.save(blocking=True)

        LanternApi.make_new_jobs()

        time.sleep(2)

        dao = LanternJob()
        jobs = [job for job in dao.iterall()]
        assert len(jobs) == 1

        assert len(CREATED_JOBS) == 1
        job = CREATED_JOBS[0]

        assert job["email"] == "*****@*****.**"
        assert len(job["list"]) == 1
예제 #12
0
    def test_09_merge_records(self):
        # Check the merge of PublicAPC records

        merge_source = PublicAPCFixtureFactory.record_merge_source()
        merge_target = PublicAPCFixtureFactory.record_merge_target()
        result = PublicAPCFixtureFactory.record_merge_result()

        source = PublicAPC()
        source.record = merge_source
        source.set_apc_ref("22222", "bbbbb")

        target = PublicAPC()
        target.record = merge_target
        target.set_apc_ref("11111", "aaaaa")

        target.merge_records(source)

        assert target.record == result
        assert target.get_apc_refs("11111") == ["aaaaa"]
        assert target.get_apc_refs("22222") == []

        # now just try some basic error cases
        with self.assertRaises(ModelException):
            target.merge_records({"random" : "data"})
예제 #13
0
    def test_08_remove_separate(self):
        # Separate an incoming Request from its corresponding PublicAPC, leaving only one owner behind

        source = RequestFixtureFactory.example()
        req = Request(source)
        req.owner = "test"

        # create a record with 2 distinct apcs from different owners
        source2 = PublicAPCFixtureFactory.example()
        apc_record = PublicAPCFixtureFactory.apc_record()
        del apc_record["ref"]  # do this so that the ref gets created correctly later
        pub = PublicAPC(source2)
        pub.add_apc_for_owner("test", apc_record)
        pub.save(blocking=True)

        # now request the removal
        PublicApi.remove(req)
        time.sleep(2)

        dao = PublicAPC()
        pub2 = dao.pull(pub.id)

        assert len(pub2.get_apcs_by_owner("test")) == 0
        assert len(pub2.get_apcs_by_owner("abcdefg")) == 1
예제 #14
0
    def test_07_separate_records(self):
        # Separate an incoming Request from its corresponding PublicAPC

        apc_record = PublicAPCFixtureFactory.apc_record()

        req = Request()
        req.owner = "test"

        pub = PublicAPC()
        pub.add_apc_for_owner("test", apc_record)
        pub.add_apc_for_owner("test", apc_record)

        assert len(pub.apc_records) == 2

        PublicApi.separate_records(req, pub)

        assert not pub.has_apcs()
예제 #15
0
    def test_06_public_dao(self):
        # Check all the DAO methods on the PublicAPC object

        dao = PublicAPC()

        source = PublicAPCFixtureFactory.example()
        pub = PublicAPC(source)
        pub.set_apc_ref("test1", "1111111111")
        pub.save(blocking=True)

        # first try the straight-forward pull
        pub2 = dao.pull(pub.id)
        assert pub2 is not None

        # now do the successful queries
        res = dao.find_by_doi("10.1234/me")
        assert len(res) == 1

        res = dao.find_by_pmid("87654321")
        assert len(res) == 1

        res = dao.find_by_pmcid("PMC1234")
        assert len(res) == 1

        res = dao.find_by_url("http://example.com/whatever")
        assert len(res) == 1

        # now to check those queries don't always return, make sure we can get 0 results
        res = dao.find_by_doi("10.1234/whatever")
        assert len(res) == 0

        res = dao.find_by_pmid("88888888")
        assert len(res) == 0

        res = dao.find_by_pmcid("PMC1111")
        assert len(res) == 0

        res = dao.find_by_url("http://example.com/another")
        assert len(res) == 0

        gen = dao.list_by_owner("abcdefg")
        count = 0
        for apc in gen:
            count += 1
        assert count == 1
예제 #16
0
    def test_05_pull_then_update(self):
        # Pull a record through the ApiRequest object and then udpate it

        acc = MonitorUKAccount()
        acc.id = "abcdefghij"
        acc.save(blocking=True)

        pub_source = PublicAPCFixtureFactory.example()
        del pub_source["id"]
        pub = PublicAPC(pub_source)
        pub.save(blocking=True)

        pub_source2 = deepcopy(pub_source.get("record"))
        pub_source2["dc:title"] = "An update"
        result = ApiRequest.pull(pub.id, account=acc)
        result.update(pub_source2)
        assert result.raw == pub_source2
        assert result.account.id == acc.id
예제 #17
0
    def test_06_get_id(self):
        # Get the operative ID of a record

        acc = MonitorUKAccount()
        acc.id = "abcdefghij"
        source = RequestFixtureFactory.record()

        # first, provide a record which has a doi, and ensure that's the id we get back
        req = ApiRequest(source, account=acc)
        assert req.id == "10.1234/me"

        # now make a public record, and check we get the right ids for it
        pub_source = PublicAPCFixtureFactory.example()
        del pub_source["id"]
        del pub_source["record"]["dc:identifier"]
        pub = PublicAPC(pub_source)
        pub.save(blocking=True)

        result = ApiRequest.pull(pub.id, account=acc)
        assert result.id == pub.id
        assert result.id == result.public_id
예제 #18
0
    def test_04_merge_public_apcs(self):
        # Merge two PublicAPC records together

        source_source = PublicAPCFixtureFactory.example()
        target_source = PublicAPCFixtureFactory.example()

        # first try a merge with no apc records (this shouldn't ever happen in normal operation)
        ss1 = deepcopy(source_source)
        del ss1["record"]["jm:apc"]
        source1 = PublicAPC(ss1)

        ts1 = deepcopy(target_source)
        del ts1["record"]["jm:apc"]
        target1 = PublicAPC(ts1)

        result = PublicApi.merge_public_apcs(source1, target1)
        assert len(result.apc_records) == 0

        # next try a merge with only apc records in the source (again, shouldn't really happen in real life)
        ss2 = deepcopy(source_source)
        source2 = PublicAPC(ss2)

        ts2 = deepcopy(target_source)
        del ts2["record"]["jm:apc"]
        target2 = PublicAPC(ts2)

        result = PublicApi.merge_public_apcs(source2, target2)
        assert len(result.apc_records) == 1

        # next try a merge with only apc records in the target (also shouldn't happen in real life)
        ss3 = deepcopy(source_source)
        source3 = PublicAPC(ss3)

        ts3 = deepcopy(target_source)
        del ts3["record"]["jm:apc"]
        target3 = PublicAPC(ts3)

        result = PublicApi.merge_public_apcs(source3, target3)
        assert len(result.apc_records) == 1

        # finally try a merge with the following criteria:
        # replacements apcs and new apcs in the source record
        # existing apcs and other apcs in the target record
        apc_record = PublicAPCFixtureFactory.apc_record()

        first = deepcopy(apc_record)
        first["ref"] = "aaaaa"
        second = deepcopy(apc_record)
        second["ref"] = "bbbbb"
        ss4 = deepcopy(source_source)
        del ss4["record"]["jm:apc"]
        source4 = PublicAPC(ss4)
        source4.add_apc_for_owner("11111", first)
        source4.add_apc_for_owner("11111", second)

        third = deepcopy(apc_record)
        third["ref"] = "ccccc"
        fourth = deepcopy(apc_record)
        fourth["ref"] = "ddddd"
        ts4 = deepcopy(target_source)
        del ts4["record"]["jm:apc"]
        target4 = PublicAPC(ts4)
        target4.add_apc_for_owner("11111", third)
        target4.add_apc_for_owner("22222", fourth)

        result = PublicApi.merge_public_apcs(source4, target4)
        assert len(result.apc_records) == 3

        ones = result.get_apcs_by_owner("11111")
        assert len(ones) == 2
        refs = [o.get("ref") for o in ones]
        assert "aaaaa" in refs
        assert "bbbbb" in refs
        assert "ccccc" not in refs
        assert "ddddd" not in refs

        twos = result.get_apcs_by_owner("22222")
        assert len(twos) == 1
        refs = [o.get("ref") for o in twos]
        assert "aaaaa" not in refs
        assert "bbbbb" not in refs
        assert "ccccc" not in refs
        assert "ddddd" in refs
예제 #19
0
    def test_04_create_job(self):
        # Check we can create jobs correctly

        acc1 = MonitorUKAccount()
        acc1.email = "*****@*****.**"
        acc1.save()

        acc2 = MonitorUKAccount()
        acc2.email = "*****@*****.**"
        acc2.lantern_email = "*****@*****.**"
        acc2.lantern_api_key = "123456789"
        acc2.save()

        acc3 = MonitorUKAccount()
        acc3.email = "*****@*****.**"
        acc3.lantern_email = "*****@*****.**"
        acc3.lantern_api_key = "987654321"
        acc3.save(blocking=True)

        # a record which does not need lantern
        source = PublicAPCFixtureFactory.make_record(acc2.id, None, None, None)
        source["admin"]["lantern_lookup"] = dates.now()
        pub = PublicAPC(source)
        pub.save()

        # a record that needs lantern because of a missing field
        source = PublicAPCFixtureFactory.make_record(acc2.id, None, None, None)
        del source["admin"]["lantern_lookup"]
        del source["record"]["rioxxterms:publication_date"]
        pub = PublicAPC(source)
        pub.save()

        # a record that needs lantern because it has timed out and has a missing field
        source = PublicAPCFixtureFactory.make_record(acc2.id, None, None, None)
        source["admin"]["lantern_lookup"] = dates.format(dates.before_now(31104000))   # a year ago
        del source["record"]["rioxxterms:publication_date"]
        pub = PublicAPC(source)
        pub.save()

        # a record that needs lantern but has no identifiers
        source = PublicAPCFixtureFactory.make_record(acc2.id, None, None, None)
        source["admin"]["lantern_lookup"] = dates.format(dates.before_now(31104000))   # a year ago
        del source["record"]["rioxxterms:publication_date"]
        del source["record"]["dc:identifier"]
        pub = PublicAPC(source)
        pub.save()

        # a record which does not need lantern
        source = PublicAPCFixtureFactory.make_record(acc3.id, None, None, None)
        source["admin"]["lantern_lookup"] = dates.now()
        pub = PublicAPC(source)
        pub.save()

        # a record that needs lantern because of a missing field
        source = PublicAPCFixtureFactory.make_record(acc3.id, None, None, None)
        del source["admin"]["lantern_lookup"]
        del source["record"]["rioxxterms:publication_date"]
        pub = PublicAPC(source)
        pub.save()

        # a record that needs lantern because it has timed out and has a missing field
        source = PublicAPCFixtureFactory.make_record(acc3.id, None, None, None)
        source["admin"]["lantern_lookup"] = dates.format(dates.before_now(31104000))   # a year ago
        del source["record"]["rioxxterms:publication_date"]
        pub = PublicAPC(source)
        pub.save()

        # a record that needs lantern but has no identifiers
        source = PublicAPCFixtureFactory.make_record(acc3.id, None, None, None)
        source["admin"]["lantern_lookup"] = dates.format(dates.before_now(31104000))   # a year ago
        del source["record"]["rioxxterms:publication_date"]
        del source["record"]["dc:identifier"]
        pub = PublicAPC(source)
        pub.save(blocking=True)

        LanternApi.make_new_jobs()

        time.sleep(2)

        dao = LanternJob()
        jobs = [job for job in dao.iterall()]
        assert len(jobs) == 2

        assert len(CREATED_JOBS) == 2
        count = 0
        for job in CREATED_JOBS:
            if job["email"] == "*****@*****.**":
                count += 1
                assert len(job["list"]) == 2
            elif job["email"] == "*****@*****.**":
                count += 10
                assert len(job["list"]) == 2
        assert count == 11

        # now do the same thing again.  The jobs should not change, as we've already created jobs
        # for all the public records
        LanternApi.make_new_jobs()
        time.sleep(2)
        jobs = [job for job in dao.iterall()]
        assert len(jobs) == 2
예제 #20
0
    def test_13_process_ehnancements_cycle(self):
        # Run through the process of processing an enhancement

        source = EnhancementFixtureFactory.example()
        if "id" in source:
            del source["id"]

        pub_dao = PublicAPC()
        wfs_dao = WorkflowState()

        # first make a public record for us to enhance
        first = PublicAPCFixtureFactory.example()
        del first["record"]["dc:title"]
        pub = PublicAPC(first)
        pub.save(blocking=True)

        # now create an enhancements on the record
        second = deepcopy(source)
        second["record"]["dc:title"] = "Update"
        second["created_date"] = "2002-01-01T00:00:00Z"
        en = Enhancement(second)
        en.public_id = pub.id
        en.save(blocking=True)

        # run the job
        WorkflowApi.process_enhancements()

        time.sleep(2)

        # check that the workflow state was created
        wfs = wfs_dao.pull("enhancements")
        assert wfs is not None
        assert wfs.last_request == en.created_date
        assert wfs.already_processed == [en.id]

        # check the public record was updated
        pubs = pub_dao.find_by_doi("10.1234/me")
        assert len(pubs) == 1
        assert pubs[0].record.get("dc:title") == "Update"

        # now run an update with the same date, to observe the difference in the workflow state
        third = deepcopy(source)
        third["record"]["dc:title"] = "Update 2"
        third["created_date"] = "2002-01-01T00:00:00Z"
        en2 = Enhancement(third)
        en2.public_id = pub.id
        en2.save(blocking=True)

        # run the job again
        WorkflowApi.process_enhancements()

        time.sleep(2)

        # check the public record was updated
        pubs = pub_dao.find_by_doi("10.1234/me")
        assert len(pubs) == 1
        assert (
            pubs[0].record.get("dc:title") == "Update"
        )  # should not have been updated, since data was already present

        # check that the workflow state was updated
        wfs = wfs_dao.pull("enhancements")
        assert wfs is not None
        assert wfs.last_request == en2.created_date
        assert wfs.already_processed == [en.id, en2.id]  # processed records should have been appended