001package org.fcrepo.migration.handlers.ocfl;
002
003import com.fasterxml.jackson.annotation.JsonInclude;
004import com.fasterxml.jackson.databind.ObjectMapper;
005import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
006import edu.wisc.library.ocfl.api.MutableOcflRepository;
007import edu.wisc.library.ocfl.api.model.FileDetails;
008import edu.wisc.library.ocfl.api.model.ObjectVersionId;
009import edu.wisc.library.ocfl.core.OcflRepositoryBuilder;
010import edu.wisc.library.ocfl.core.extension.storage.layout.config.HashedNTupleLayoutConfig;
011import edu.wisc.library.ocfl.core.path.mapper.LogicalPathMappers;
012import edu.wisc.library.ocfl.core.storage.filesystem.FileSystemOcflStorage;
013import org.apache.commons.codec.digest.DigestUtils;
014import org.apache.commons.io.IOUtils;
015import org.apache.commons.lang3.SystemUtils;
016import org.fcrepo.migration.ContentDigest;
017import org.fcrepo.migration.DatastreamInfo;
018import org.fcrepo.migration.DatastreamVersion;
019import org.fcrepo.migration.DefaultObjectInfo;
020import org.fcrepo.migration.MigrationType;
021import org.fcrepo.migration.ObjectProperties;
022import org.fcrepo.migration.ObjectProperty;
023import org.fcrepo.migration.ObjectVersionReference;
024import org.fcrepo.migration.ResourceMigrationType;
025import org.fcrepo.storage.ocfl.CommitType;
026import org.fcrepo.storage.ocfl.DefaultOcflObjectSessionFactory;
027import org.fcrepo.storage.ocfl.InteractionModel;
028import org.fcrepo.storage.ocfl.OcflObjectSession;
029import org.fcrepo.storage.ocfl.OcflObjectSessionFactory;
030import org.fcrepo.storage.ocfl.PersistencePaths;
031import org.fcrepo.storage.ocfl.ResourceHeadersVersion;
032import org.fcrepo.storage.ocfl.cache.NoOpCache;
033import org.junit.Before;
034import org.junit.Rule;
035import org.junit.Test;
036import org.junit.rules.TemporaryFolder;
037import org.mockito.Mockito;
038import org.mockito.stubbing.Answer;
039
040import java.io.ByteArrayInputStream;
041import java.io.IOException;
042import java.io.InputStream;
043import java.io.UncheckedIOException;
044import java.nio.charset.StandardCharsets;
045import java.nio.file.Files;
046import java.nio.file.Path;
047import java.time.Instant;
048import java.util.List;
049import java.util.Objects;
050
051import static com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS;
052import static org.hamcrest.MatcherAssert.assertThat;
053import static org.hamcrest.Matchers.allOf;
054import static org.hamcrest.Matchers.containsString;
055import static org.hamcrest.Matchers.not;
056import static org.junit.Assert.assertEquals;
057import static org.junit.Assert.assertFalse;
058import static org.junit.Assert.assertNotEquals;
059import static org.junit.Assert.assertNull;
060import static org.junit.Assert.assertTrue;
061import static org.mockito.Mockito.doReturn;
062import static org.mockito.Mockito.when;
063
064/**
065 * @author pwinckles
066 */
067public class ArchiveGroupHandlerTest {
068
069    private static final String FCREPO_ROOT = "info:fedora/";
070    private static final String USER = "fedoraAdmin";
071    private static final String INLINE = "X";
072    private static final String PROXY = "E";
073    private static final String REDIRECT = "R";
074    private static final String MANAGED = "M";
075    private static final String DS_ACTIVE = "A";
076    private static final String DS_INACTIVE = "I";
077    private static final String DS_DELETED = "D";
078    private static final String OBJ_ACTIVE = "Active";
079    private static final String OBJ_INACTIVE = "Inactive";
080    private static final String OBJ_DELETED = "Deleted";
081    private static final String RELS_INT = "RELS-INT";
082    private static final String RELS_EXT = "RELS-EXT";
083
084    @Rule
085    public TemporaryFolder tempDir = new TemporaryFolder();
086
087    private Path ocflRoot;
088    private Path staging;
089
090    private MutableOcflRepository ocflRepo;
091    private OcflObjectSessionFactory sessionFactory;
092    private OcflObjectSessionFactory plainSessionFactory;
093
094    private ObjectMapper objectMapper;
095    private String date;
096    private ResourceMigrationType resourceMigrationType;
097
098    @Before
099    public void setup() throws IOException {
100        ocflRoot = tempDir.newFolder("ocfl").toPath();
101        staging = tempDir.newFolder("staging").toPath();
102
103        final var logicalPathMapper = SystemUtils.IS_OS_WINDOWS ?
104                LogicalPathMappers.percentEncodingWindowsMapper() : LogicalPathMappers.percentEncodingLinuxMapper();
105
106        ocflRepo = new OcflRepositoryBuilder()
107                .defaultLayoutConfig(new HashedNTupleLayoutConfig())
108                .logicalPathMapper(logicalPathMapper)
109                .storage(FileSystemOcflStorage.builder().repositoryRoot(ocflRoot).build())
110                .workDir(staging)
111                .buildMutable();
112
113        objectMapper = new ObjectMapper()
114                .configure(WRITE_DATES_AS_TIMESTAMPS, false)
115                .registerModule(new JavaTimeModule())
116                .setSerializationInclusion(JsonInclude.Include.NON_NULL);
117
118        sessionFactory = new DefaultOcflObjectSessionFactory(ocflRepo, staging, objectMapper,
119                new NoOpCache<>(), new NoOpCache<>(), CommitType.NEW_VERSION,
120                "testing", USER, "info:fedora/fedoraAdmin");
121
122        plainSessionFactory = new PlainOcflObjectSessionFactory(ocflRepo, staging,
123                "testing", USER, "info:fedora/fedoraAdmin", false);
124
125        date = Instant.now().toString().substring(0, 10);
126        resourceMigrationType = ResourceMigrationType.ARCHIVAL;
127    }
128
129    @Test
130    public void processObjectSingleVersionF6Format() throws IOException {
131        final var handler = createHandler(MigrationType.FEDORA_OCFL, false, false);
132
133        final var pid = "obj1";
134        final var dsId1 = "ds1";
135        final var dsId2 = "ds2";
136
137        final var ds1 = datastreamVersion(dsId1, true, MANAGED, "text/plain", "hello", null);
138        final var ds2 = datastreamVersion(dsId2, true, INLINE, "application/xml", "<xml>goodbye</xml>", null);
139        when(ds2.getSize()).thenReturn(100L);
140
141        handler.processObjectVersions(List.of(
142                objectVersionReference(pid, true, List.of(ds1, ds2))
143        ), new DefaultObjectInfo(pid, pid, Files.createTempFile(tempDir.getRoot().toPath(), "foxml", "xml")));
144
145        final var ocflObjectId = addPrefix(pid);
146        final var session = sessionFactory.newSession(ocflObjectId);
147
148        verifyObjectRdf(contentToString(session, ocflObjectId));
149        verifyObjectHeaders(session, ocflObjectId);
150
151        verifyBinary(contentToString(session, ocflObjectId, dsId1), ds1);
152        verifyHeaders(session, ocflObjectId, dsId1, ds1);
153        verifyDescRdf(session, ocflObjectId, dsId1, ds1);
154        verifyDescHeaders(session, ocflObjectId, dsId1);
155
156        verifyBinary(contentToString(session, ocflObjectId, dsId2), ds2);
157        verifyHeaders(session, ocflObjectId, dsId2, ds2);
158        verifyDescRdf(session, ocflObjectId, dsId2, ds2);
159        verifyDescHeaders(session, ocflObjectId, dsId2);
160    }
161
162    @Test
163    public void processObjectMultipleVersionsF6Format() throws IOException {
164        final var handler = createHandler(MigrationType.FEDORA_OCFL, false, false);
165
166        final var pid = "obj2";
167        final var dsId1 = "ds3";
168        final var dsId2 = "ds4";
169
170        final var ds1V1 = datastreamVersion(dsId1, true, MANAGED, "application/xml", "<h1>hello</h1>", null);
171        final var ds2V1 = datastreamVersion(dsId2, true, MANAGED, "text/plain", "goodbye", null);
172
173        final var ds2V2 = datastreamVersion(dsId2, false, MANAGED, "text/plain", "fedora", null);
174
175        handler.processObjectVersions(List.of(
176                objectVersionReference(pid, true, List.of(ds1V1, ds2V1)),
177                objectVersionReference(pid, false, List.of(ds2V2))
178        ), new DefaultObjectInfo(pid, pid, Files.createTempFile(tempDir.getRoot().toPath(), "foxml", "xml")));
179
180        final var ocflObjectId = addPrefix(pid);
181        final var session = sessionFactory.newSession(ocflObjectId);
182
183        verifyObjectRdf(contentToString(session, ocflObjectId));
184        verifyObjectHeaders(session, ocflObjectId);
185
186        verifyBinary(contentToString(session, ocflObjectId, dsId1), ds1V1);
187        verifyHeaders(session, ocflObjectId, dsId1, ds1V1);
188        verifyDescRdf(session, ocflObjectId, dsId1, ds1V1);
189        verifyDescHeaders(session, ocflObjectId, dsId1);
190
191        verifyBinary(contentVersionToString(session, ocflObjectId, dsId2, "v1"), ds2V1);
192        verifyHeaders(session, ocflObjectId, dsId2, ds2V1, "v1");
193        verifyDescRdf(session, ocflObjectId, dsId2, ds2V1, "v1");
194        verifyDescHeaders(session, ocflObjectId, dsId2, "v1");
195
196        verifyBinary(contentVersionToString(session, ocflObjectId, dsId2, "v2"), ds2V2);
197        verifyHeaders(session, ocflObjectId, dsId2, ds2V2, "v2");
198        verifyDescRdf(session, ocflObjectId, dsId2, ds2V2, "v2");
199        verifyDescHeaders(session, ocflObjectId, dsId2, "v2");
200    }
201
202    @Test
203    public void processObjectMultipleVersionsAtomic() throws IOException {
204        resourceMigrationType = ResourceMigrationType.ATOMIC;
205        final var handler = createHandler(MigrationType.FEDORA_OCFL, false, true);
206
207        final var pid = "obj2";
208        final var dsId1 = "ds3";
209        final var dsId2 = "ds4";
210
211        final var ds1V1 = datastreamVersion(dsId1, true, MANAGED, "application/xml", "<h1>hello</h1>", null);
212        final var ds2V1 = datastreamVersion(dsId2, true, MANAGED, "text/plain", "goodbye", null);
213
214        final var ds2V2 = datastreamVersion(dsId2, false, MANAGED, "text/plain", "fedora", null);
215
216        handler.processObjectVersions(List.of(
217                objectVersionReference(pid, true, List.of(ds1V1, ds2V1)),
218                objectVersionReference(pid, false, List.of(ds2V2))
219        ), new DefaultObjectInfo(pid, pid, Files.createTempFile(tempDir.getRoot().toPath(), "foxml", "xml")));
220
221        final var ocflObjectId = addPrefix(pid);
222        final var objectSession = sessionFactory.newSession(ocflObjectId);
223        final var ds1Session = sessionFactory.newSession(resourceId(ocflObjectId, dsId1));
224        final var ds2Session = sessionFactory.newSession(resourceId(ocflObjectId, dsId2));
225
226        verifyObjectRdf(contentToString(objectSession, ocflObjectId));
227        verifyObjectHeaders(objectSession, ocflObjectId);
228
229        verifyBinary(contentToString(ds1Session, ocflObjectId, dsId1), ds1V1);
230        verifyHeaders(ds1Session, ocflObjectId, dsId1, ds1V1);
231        verifyDescRdf(ds1Session, ocflObjectId, dsId1, ds1V1);
232        verifyDescHeaders(ds1Session, ocflObjectId, dsId1);
233
234        verifyBinary(contentVersionToString(ds2Session, ocflObjectId, dsId2, "v1"), ds2V1);
235        verifyHeaders(ds2Session, ocflObjectId, dsId2, ds2V1, "v1");
236        verifyDescRdf(ds2Session, ocflObjectId, dsId2, ds2V1, "v1");
237        verifyDescHeaders(ds2Session, ocflObjectId, dsId2, "v1");
238
239        verifyBinary(contentVersionToString(ds2Session, ocflObjectId, dsId2, "v2"), ds2V2);
240        verifyHeaders(ds2Session, ocflObjectId, dsId2, ds2V2, "v2");
241        verifyDescRdf(ds2Session, ocflObjectId, dsId2, ds2V2, "v2");
242        verifyDescHeaders(ds2Session, ocflObjectId, dsId2, "v2");
243    }
244
245    @Test
246    public void updateFilenameFromRelsInt() throws IOException {
247        final var handler = createHandler(MigrationType.FEDORA_OCFL, false, false);
248
249        final var pid = "obj2";
250        final var dsId1 = "ds3";
251
252        final var ds1V1 = datastreamVersion(dsId1, true, MANAGED, "application/xml", "<h1>hello</h1>", null);
253        final var relsIntV1 = datastreamVersion(RELS_INT, true, MANAGED, "application/rdf+xml",
254                "<rdf:RDF xmlns:fedora-model=\"info:fedora/fedora-system:def/model#\"" +
255                        " xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n" +
256                        "\t<rdf:Description rdf:about=\"info:fedora/obj2/ds3\">\n" +
257                        "\t\t<fedora-model:downloadFilename>example.xml</fedora-model:downloadFilename>\n" +
258                        "\t</rdf:Description>\n" +
259                        "</rdf:RDF>", null);
260
261        handler.processObjectVersions(List.of(
262                objectVersionReference(pid, true, List.of(ds1V1)),
263                objectVersionReference(pid, false, List.of(relsIntV1))
264        ), new DefaultObjectInfo(pid, pid, Files.createTempFile(tempDir.getRoot().toPath(), "foxml", "xml")));
265
266        final var ocflObjectId = addPrefix(pid);
267        final var session = sessionFactory.newSession(ocflObjectId);
268
269        verifyObjectRdf(contentToString(session, ocflObjectId));
270        verifyObjectHeaders(session, ocflObjectId);
271
272        verifyBinary(contentVersionToString(session, ocflObjectId, dsId1, "v1"), ds1V1);
273        verifyHeaders(session, ocflObjectId, dsId1, ds1V1, "v1");
274        verifyDescRdf(session, ocflObjectId, dsId1, ds1V1, "v1");
275        verifyDescHeaders(session, ocflObjectId, dsId1, "v1");
276
277        verifyBinary(contentVersionToString(session, ocflObjectId, dsId1, "v2"), ds1V1);
278        verifyHeaders(session, ocflObjectId, dsId1, ds1V1, "v2", "example.xml");
279        verifyDescRdf(session, ocflObjectId, dsId1, ds1V1, "v2");
280        verifyDescHeaders(session, ocflObjectId, dsId1, "v2");
281    }
282
283    @Test
284    public void filenameRemovedFromRelsInt() throws IOException {
285        final var handler = createHandler(MigrationType.FEDORA_OCFL, false, false);
286
287        final var pid = "obj2";
288        final var dsId1 = "ds3";
289
290        final var ds1V1 = datastreamVersion(dsId1, true, MANAGED, "application/xml", "<h1>hello</h1>", null);
291        final var relsIntV1 = datastreamVersion(RELS_INT, true, MANAGED, "application/rdf+xml",
292                "<rdf:RDF xmlns:fedora-model=\"info:fedora/fedora-system:def/model#\"" +
293                        " xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n" +
294                        "\t<rdf:Description rdf:about=\"info:fedora/obj2/ds3\">\n" +
295                        "\t\t<fedora-model:downloadFilename>example.xml</fedora-model:downloadFilename>\n" +
296                        "\t</rdf:Description>\n" +
297                        "</rdf:RDF>", null);
298        final var relsIntV2 = datastreamVersion(RELS_INT, false, MANAGED, "application/rdf+xml",
299                "<rdf:RDF xmlns:fedora-model=\"info:fedora/fedora-system:def/model#\"" +
300                        " xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n" +
301                        "</rdf:RDF>", null);
302
303        handler.processObjectVersions(List.of(
304                objectVersionReference(pid, true, List.of(ds1V1)),
305                objectVersionReference(pid, false, List.of(relsIntV1)),
306                objectVersionReference(pid, false, List.of(relsIntV2))
307        ), new DefaultObjectInfo(pid, pid, Files.createTempFile(tempDir.getRoot().toPath(), "foxml", "xml")));
308
309        final var ocflObjectId = addPrefix(pid);
310        final var session = sessionFactory.newSession(ocflObjectId);
311
312        verifyObjectRdf(contentToString(session, ocflObjectId));
313        verifyObjectHeaders(session, ocflObjectId);
314
315        verifyBinary(contentVersionToString(session, ocflObjectId, dsId1, "v1"), ds1V1);
316        verifyHeaders(session, ocflObjectId, dsId1, ds1V1, "v1");
317        verifyDescRdf(session, ocflObjectId, dsId1, ds1V1, "v1");
318        verifyDescHeaders(session, ocflObjectId, dsId1, "v1");
319
320        verifyBinary(contentVersionToString(session, ocflObjectId, dsId1, "v2"), ds1V1);
321        verifyHeaders(session, ocflObjectId, dsId1, ds1V1, "v2", "example.xml");
322        verifyDescRdf(session, ocflObjectId, dsId1, ds1V1, "v2");
323        verifyDescHeaders(session, ocflObjectId, dsId1, "v2");
324
325        verifyBinary(contentVersionToString(session, ocflObjectId, dsId1, "v3"), ds1V1);
326        verifyHeaders(session, ocflObjectId, dsId1, ds1V1, "v3", "ds3-label");
327        verifyDescRdf(session, ocflObjectId, dsId1, ds1V1, "v3");
328        verifyDescHeaders(session, ocflObjectId, dsId1, "v3");
329    }
330
331    @Test
332    public void filenameRemovedFromRelsIntAndLabelChanged() throws IOException {
333        final var handler = createHandler(MigrationType.FEDORA_OCFL, true, false);
334
335        final var pid = "obj2";
336        final var dsId1 = "ds3";
337
338        final var ds1V1 = datastreamVersion(dsId1, true, MANAGED, "application/xml", "<h1>hello</h1>", null);
339        final var relsIntV1 = datastreamVersion(RELS_INT, true, MANAGED, "application/rdf+xml",
340                "<rdf:RDF xmlns:fedora-model=\"info:fedora/fedora-system:def/model#\"" +
341                        " xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n" +
342                        "\t<rdf:Description rdf:about=\"info:fedora/obj2/ds3\">\n" +
343                        "\t\t<fedora-model:downloadFilename>example.xml</fedora-model:downloadFilename>\n" +
344                        "\t</rdf:Description>\n" +
345                        "</rdf:RDF>", null);
346
347        final var ds1V2 = datastreamVersion(dsId1, false, MANAGED, "application/xml", "<h1>hello</h1>",
348                DS_ACTIVE, null, "test");
349        final var relsIntV2 = datastreamVersion(RELS_INT, false, MANAGED, "application/rdf+xml",
350                "<rdf:RDF xmlns:fedora-model=\"info:fedora/fedora-system:def/model#\"" +
351                        " xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n" +
352                        "</rdf:RDF>", null);
353
354        handler.processObjectVersions(List.of(
355                objectVersionReference(pid, true, List.of(ds1V1)),
356                objectVersionReference(pid, false, List.of(relsIntV1)),
357                objectVersionReference(pid, false, List.of(relsIntV2, ds1V2))
358        ), new DefaultObjectInfo(pid, pid, Files.createTempFile(tempDir.getRoot().toPath(), "foxml", "xml")));
359
360        final var ocflObjectId = addPrefix(pid);
361        final var session = sessionFactory.newSession(ocflObjectId);
362
363        verifyObjectRdf(contentToString(session, ocflObjectId));
364        verifyObjectHeaders(session, ocflObjectId);
365
366        verifyBinary(contentVersionToString(session, ocflObjectId, dsId1, "v1"), ds1V1);
367        verifyHeaders(session, ocflObjectId, dsId1, ds1V1, "v1", "ds3-label.xml");
368        verifyDescRdf(session, ocflObjectId, dsId1, ds1V1, "v1");
369        verifyDescHeaders(session, ocflObjectId, dsId1, "v1");
370
371        verifyBinary(contentVersionToString(session, ocflObjectId, dsId1, "v2"), ds1V1);
372        verifyHeaders(session, ocflObjectId, dsId1, ds1V1, "v2", "example.xml");
373        verifyDescRdf(session, ocflObjectId, dsId1, ds1V1, "v2");
374        verifyDescHeaders(session, ocflObjectId, dsId1, "v2");
375
376        verifyBinary(contentVersionToString(session, ocflObjectId, dsId1, "v3"), ds1V1);
377        verifyHeaders(session, ocflObjectId, dsId1, ds1V2, "v3", "test.xml");
378        verifyDescRdf(session, ocflObjectId, dsId1, ds1V2, "v3");
379        verifyDescHeaders(session, ocflObjectId, dsId1, "v3");
380    }
381
382    @Test
383    public void addRelsTriples() throws IOException {
384        final var handler = createHandler(MigrationType.FEDORA_OCFL, false, false);
385
386        final var pid = "obj2";
387        final var dsId1 = "ds3";
388        final var dsId2 = "ds4";
389
390        final var ds1V1 = datastreamVersion(dsId1, true, MANAGED, "application/xml", "<h1>hello</h1>", null);
391        final var ds2V1 = datastreamVersion(dsId2, true, MANAGED, "text/plain", "goodbye", null);
392        final var relsExtV1 = datastreamVersion(RELS_EXT, true, MANAGED, "application/rdf+xml",
393                "<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n" +
394                        "\t<rdf:Description rdf:about=\"info:fedora/obj2\">\n" +
395                        "\t\t<hasModel xmlns=\"info:fedora/fedora-system:def/model#\"" +
396                        " rdf:resource=\"info:fedora/TestObject\"></hasModel>\n" +
397                        "\t\t<isMemberOf xmlns=\"info:fedora/fedora-system:def/relations-external#\"" +
398                        " rdf:resource=\"info:fedora/obj1\"></isMemberOf>\n" +
399                        "\t</rdf:Description>\n" +
400                        "</rdf:RDF>", null);
401        final var relsIntV1 = datastreamVersion(RELS_INT, true, MANAGED, "application/rdf+xml",
402                "<rdf:RDF xmlns:fedora-model=\"info:fedora/fedora-system:def/model#\"" +
403                        " xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"" +
404                        " xmlns:example=\"http://example.com/#\">\n" +
405                        "\t<rdf:Description rdf:about=\"info:fedora/obj2/ds3\">\n" +
406                        "\t\t<example:animal>cat</example:animal>\n" +
407                        "\t</rdf:Description>\n" +
408                        "\t<rdf:Description rdf:about=\"info:fedora/obj2/ds4\">\n" +
409                        "\t\t<example:animal>dog</example:animal>\n" +
410                        "\t</rdf:Description>\n" +
411                        "</rdf:RDF>", null);
412
413        final var ds2V2 = datastreamVersion(dsId2, false, MANAGED, "text/plain", "fedora", null);
414        final var relsExtV2 = datastreamVersion(RELS_EXT, false, MANAGED, "application/rdf+xml",
415                "<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n" +
416                        "\t<rdf:Description rdf:about=\"info:fedora/obj2\">\n" +
417                        "\t\t<hasModel xmlns=\"info:fedora/fedora-system:def/model#\"" +
418                        " rdf:resource=\"info:fedora/ExampleObject\"></hasModel>\n" +
419                        "\t\t<isMemberOf xmlns=\"info:fedora/fedora-system:def/relations-external#\"" +
420                        " rdf:resource=\"info:fedora/obj1\"></isMemberOf>\n" +
421                        "\t</rdf:Description>\n" +
422                        "</rdf:RDF>", null);
423        final var relsIntV2 = datastreamVersion(RELS_INT, false, MANAGED, "application/rdf+xml",
424                "<rdf:RDF xmlns:fedora-model=\"info:fedora/fedora-system:def/model#\"" +
425                        " xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"" +
426                        " xmlns:example=\"http://example.com/#\">\n" +
427                        "\t<rdf:Description rdf:about=\"info:fedora/obj2/ds3\">\n" +
428                        "\t\t<example:animal>cat</example:animal>\n" +
429                        "\t</rdf:Description>\n" +
430                        "\t<rdf:Description rdf:about=\"info:fedora/obj2/ds4\">\n" +
431                        "\t\t<example:animal>frog</example:animal>\n" +
432                        "\t</rdf:Description>\n" +
433                        "</rdf:RDF>", null);
434
435        handler.processObjectVersions(List.of(
436                objectVersionReference(pid, true, List.of(ds1V1, ds2V1, relsExtV1, relsIntV1)),
437                objectVersionReference(pid, false, List.of(ds2V2, relsExtV2)),
438                objectVersionReference(pid, false, List.of(relsIntV2))
439        ), new DefaultObjectInfo(pid, pid, Files.createTempFile(tempDir.getRoot().toPath(), "foxml", "xml")));
440
441        final var ocflObjectId = addPrefix(pid);
442        final var session = sessionFactory.newSession(ocflObjectId);
443
444        final var objectRdfV1 = contentVersionToString(session, ocflObjectId, "v1");
445        verifyObjectRdf(objectRdfV1);
446        assertThat(objectRdfV1, allOf(containsString("TestObject"), containsString("obj1")));
447        verifyObjectHeaders(session, ocflObjectId);
448
449        verifyBinary(contentToString(session, ocflObjectId, dsId1), ds1V1);
450        verifyHeaders(session, ocflObjectId, dsId1, ds1V1);
451        final var ds1V1Rdf = verifyDescRdf(session, ocflObjectId, dsId1, ds1V1);
452        assertThat(ds1V1Rdf, allOf(containsString("cat"), not(containsString("dog"))));
453        verifyDescHeaders(session, ocflObjectId, dsId1);
454
455        verifyBinary(contentVersionToString(session, ocflObjectId, dsId2, "v1"), ds2V1);
456        verifyHeaders(session, ocflObjectId, dsId2, ds2V1, "v1");
457        final var ds2V1Rdf = verifyDescRdf(session, ocflObjectId, dsId2, ds2V1, "v1");
458        assertThat(ds2V1Rdf, allOf(containsString("dog"), not(containsString("cat"))));
459        verifyDescHeaders(session, ocflObjectId, dsId2, "v1");
460
461        final var objectRdfV2 = contentToString(session, ocflObjectId);
462        verifyObjectRdf(objectRdfV2);
463        assertThat(objectRdfV2, allOf(containsString("ExampleObject"),
464                containsString("obj1"), not(containsString("TestObject"))));
465
466        verifyBinary(contentVersionToString(session, ocflObjectId, dsId2, "v2"), ds2V2);
467        verifyHeaders(session, ocflObjectId, dsId2, ds2V2, "v2");
468        final var ds2V2Rdf = verifyDescRdf(session, ocflObjectId, dsId2, ds2V2, "v2");
469        assertThat(ds2V2Rdf, allOf(containsString("dog"), not(containsString("cat"))));
470        verifyDescHeaders(session, ocflObjectId, dsId2, "v2");
471
472        final var ds2V3Rdf = verifyDescRdf(session, ocflObjectId, dsId2, ds2V2, "v3");
473        assertThat(ds2V3Rdf, allOf(containsString("frog"),
474                not(containsString("cat")), not(containsString("dog"))));
475    }
476
477    @Test
478    public void processObjectMultipleVersionsWithDeletedDsF6Format() throws IOException {
479        final var handler = createHandler(MigrationType.FEDORA_OCFL, false, false);
480
481        final var pid = "obj2";
482        final var dsId1 = "ds3";
483        final var dsId2 = "ds4";
484
485        final var ds1V1 = datastreamVersion(dsId1, true, MANAGED, "application/xml", "<h1>hello</h1>",
486                DS_INACTIVE, null, dsId1 + "-label");
487        final var ds2V1 = datastreamVersion(dsId2, true, MANAGED, "text/plain", "goodbye",
488                DS_DELETED, null, dsId2 + "-label");
489
490        final var ds2V2 = datastreamVersion(dsId2, false, MANAGED, "text/plain", "fedora",
491                DS_DELETED, null, dsId2 + "-label");
492
493        handler.processObjectVersions(List.of(
494                objectVersionReference(pid, true, List.of(ds1V1, ds2V1)),
495                objectVersionReference(pid, false, List.of(ds2V2))
496        ), new DefaultObjectInfo(pid, pid, Files.createTempFile(tempDir.getRoot().toPath(), "foxml", "xml")));
497
498        final var ocflObjectId = addPrefix(pid);
499        final var session = sessionFactory.newSession(ocflObjectId);
500
501        verifyObjectRdf(contentToString(session, ocflObjectId));
502        verifyObjectHeaders(session, ocflObjectId);
503
504        verifyBinary(contentToString(session, ocflObjectId, dsId1), ds1V1);
505        verifyHeaders(session, ocflObjectId, dsId1, ds1V1);
506        verifyDescRdf(session, ocflObjectId, dsId1, ds1V1);
507        verifyDescHeaders(session, ocflObjectId, dsId1);
508
509        verifyBinary(contentVersionToString(session, ocflObjectId, dsId2, "v1"), ds2V1);
510        verifyHeaders(session, ocflObjectId, dsId2, ds2V1, "v1");
511        verifyDescRdf(session, ocflObjectId, dsId2, ds2V1, "v1");
512        verifyDescHeaders(session, ocflObjectId, dsId2, "v1");
513
514        verifyBinary(contentVersionToString(session, ocflObjectId, dsId2, "v2"), ds2V2);
515        verifyHeaders(session, ocflObjectId, dsId2, ds2V2, "v2");
516        verifyDescRdf(session, ocflObjectId, dsId2, ds2V2, "v2");
517        verifyDescHeaders(session, ocflObjectId, dsId2, "v2");
518
519        verifyResourceDeleted(session, resourceId(ocflObjectId, dsId2));
520        verifyResourceDeleted(session, metadataId(ocflObjectId, dsId2));
521    }
522
523    @Test
524    public void processObjectMultipleVersionsAndDeleteInactiveF6Format() throws IOException {
525        final var handler = createHandler(MigrationType.FEDORA_OCFL, false, true);
526
527        final var pid = "obj2";
528        final var dsId1 = "ds3";
529        final var dsId2 = "ds4";
530
531        final var ds1V1 = datastreamVersion(dsId1, true, MANAGED, "application/xml", "<h1>hello</h1>",
532                DS_INACTIVE, null, dsId1 + "-label");
533        final var ds2V1 = datastreamVersion(dsId2, true, MANAGED, "text/plain", "goodbye",
534                DS_DELETED, null, dsId2 + "-label");
535
536        final var ds2V2 = datastreamVersion(dsId2, false, MANAGED, "text/plain", "fedora",
537                DS_DELETED, null, dsId2 + "-label");
538
539        handler.processObjectVersions(List.of(
540                objectVersionReference(pid, true, List.of(ds1V1, ds2V1)),
541                objectVersionReference(pid, false, List.of(ds2V2))
542        ), new DefaultObjectInfo(pid, pid, Files.createTempFile(tempDir.getRoot().toPath(), "foxml", "xml")));
543
544        final var ocflObjectId = addPrefix(pid);
545        final var session = sessionFactory.newSession(ocflObjectId);
546
547        verifyObjectRdf(contentToString(session, ocflObjectId));
548        verifyObjectHeaders(session, ocflObjectId);
549
550        verifyBinary(contentVersionToString(session, ocflObjectId, dsId1, "v1"), ds1V1);
551        verifyHeaders(session, ocflObjectId, dsId1, ds1V1, "v1");
552        verifyDescRdf(session, ocflObjectId, dsId1, ds1V1, "v1");
553        verifyDescHeaders(session, ocflObjectId, dsId1, "v1");
554
555        verifyBinary(contentVersionToString(session, ocflObjectId, dsId2, "v1"), ds2V1);
556        verifyHeaders(session, ocflObjectId, dsId2, ds2V1, "v1");
557        verifyDescRdf(session, ocflObjectId, dsId2, ds2V1, "v1");
558        verifyDescHeaders(session, ocflObjectId, dsId2, "v1");
559
560        verifyBinary(contentVersionToString(session, ocflObjectId, dsId2, "v2"), ds2V2);
561        verifyHeaders(session, ocflObjectId, dsId2, ds2V2, "v2");
562        verifyDescRdf(session, ocflObjectId, dsId2, ds2V2, "v2");
563        verifyDescHeaders(session, ocflObjectId, dsId2, "v2");
564
565        verifyResourceDeleted(session, resourceId(ocflObjectId, dsId1));
566        verifyResourceDeleted(session, metadataId(ocflObjectId, dsId1));
567        verifyResourceDeleted(session, resourceId(ocflObjectId, dsId2));
568        verifyResourceDeleted(session, metadataId(ocflObjectId, dsId2));
569    }
570
571    @Test
572    public void processObjectMultipleVersionsAndObjectDeletedF6Format() throws IOException {
573        final var handler = createHandler(MigrationType.FEDORA_OCFL, false, false);
574
575        final var pid = "obj2";
576        final var dsId1 = "ds3";
577        final var dsId2 = "ds4";
578
579        final var ds1V1 = datastreamVersion(dsId1, true, MANAGED, "application/xml", "<h1>hello</h1>", null);
580        final var ds2V1 = datastreamVersion(dsId2, true, MANAGED, "text/plain", "goodbye", null);
581
582        final var ds2V2 = datastreamVersion(dsId2, false, MANAGED, "text/plain", "fedora", null);
583
584        handler.processObjectVersions(List.of(
585                objectVersionReference(pid, true, OBJ_DELETED, List.of(ds1V1, ds2V1)),
586                objectVersionReference(pid, false, OBJ_DELETED, List.of(ds2V2))
587        ), new DefaultObjectInfo(pid, pid, Files.createTempFile(tempDir.getRoot().toPath(), "foxml", "xml")));
588
589        final var ocflObjectId = addPrefix(pid);
590        final var session = sessionFactory.newSession(ocflObjectId);
591
592        verifyObjectRdf(contentVersionToString(session, ocflObjectId, "v1"));
593        verifyObjectHeaders(session, ocflObjectId, "v1");
594
595        verifyBinary(contentVersionToString(session, ocflObjectId, dsId1, "v1"), ds1V1);
596        verifyHeaders(session, ocflObjectId, dsId1, ds1V1, "v1");
597        verifyDescRdf(session, ocflObjectId, dsId1, ds1V1, "v1");
598        verifyDescHeaders(session, ocflObjectId, dsId1, "v1");
599
600        verifyBinary(contentVersionToString(session, ocflObjectId, dsId2, "v1"), ds2V1);
601        verifyHeaders(session, ocflObjectId, dsId2, ds2V1, "v1");
602        verifyDescRdf(session, ocflObjectId, dsId2, ds2V1, "v1");
603        verifyDescHeaders(session, ocflObjectId, dsId2, "v1");
604
605        verifyBinary(contentVersionToString(session, ocflObjectId, dsId2, "v2"), ds2V2);
606        verifyHeaders(session, ocflObjectId, dsId2, ds2V2, "v2");
607        verifyDescRdf(session, ocflObjectId, dsId2, ds2V2, "v2");
608        verifyDescHeaders(session, ocflObjectId, dsId2, "v2");
609
610        verifyResourceDeleted(session, ocflObjectId);
611        verifyResourceDeleted(session, resourceId(ocflObjectId, dsId1));
612        verifyResourceDeleted(session, metadataId(ocflObjectId, dsId1));
613        verifyResourceDeleted(session, resourceId(ocflObjectId, dsId2));
614        verifyResourceDeleted(session, metadataId(ocflObjectId, dsId2));
615    }
616
617    @Test
618    public void processObjectMultipleVersionsAndObjectInactiveDeletedF6Format() throws IOException {
619        final var handler = createHandler(MigrationType.FEDORA_OCFL, false, true);
620
621        final var pid = "obj2";
622        final var dsId1 = "ds3";
623        final var dsId2 = "ds4";
624
625        final var ds1V1 = datastreamVersion(dsId1, true, MANAGED, "application/xml", "<h1>hello</h1>", null);
626        final var ds2V1 = datastreamVersion(dsId2, true, MANAGED, "text/plain", "goodbye", null);
627
628        final var ds2V2 = datastreamVersion(dsId2, false, MANAGED, "text/plain", "fedora", null);
629
630        handler.processObjectVersions(List.of(
631                objectVersionReference(pid, true, OBJ_INACTIVE, List.of(ds1V1, ds2V1)),
632                objectVersionReference(pid, false, OBJ_INACTIVE, List.of(ds2V2))
633        ), new DefaultObjectInfo(pid, pid, Files.createTempFile(tempDir.getRoot().toPath(), "foxml", "xml")));
634
635        final var ocflObjectId = addPrefix(pid);
636        final var session = sessionFactory.newSession(ocflObjectId);
637
638        verifyObjectRdf(contentVersionToString(session, ocflObjectId, "v1"));
639        verifyObjectHeaders(session, ocflObjectId, "v1");
640
641        verifyBinary(contentVersionToString(session, ocflObjectId, dsId1, "v1"), ds1V1);
642        verifyHeaders(session, ocflObjectId, dsId1, ds1V1, "v1");
643        verifyDescRdf(session, ocflObjectId, dsId1, ds1V1, "v1");
644        verifyDescHeaders(session, ocflObjectId, dsId1, "v1");
645
646        verifyBinary(contentVersionToString(session, ocflObjectId, dsId2, "v1"), ds2V1);
647        verifyHeaders(session, ocflObjectId, dsId2, ds2V1, "v1");
648        verifyDescRdf(session, ocflObjectId, dsId2, ds2V1, "v1");
649        verifyDescHeaders(session, ocflObjectId, dsId2, "v1");
650
651        verifyBinary(contentVersionToString(session, ocflObjectId, dsId2, "v2"), ds2V2);
652        verifyHeaders(session, ocflObjectId, dsId2, ds2V2, "v2");
653        verifyDescRdf(session, ocflObjectId, dsId2, ds2V2, "v2");
654        verifyDescHeaders(session, ocflObjectId, dsId2, "v2");
655
656        verifyResourceDeleted(session, ocflObjectId);
657        verifyResourceDeleted(session, resourceId(ocflObjectId, dsId1));
658        verifyResourceDeleted(session, metadataId(ocflObjectId, dsId1));
659        verifyResourceDeleted(session, resourceId(ocflObjectId, dsId2));
660        verifyResourceDeleted(session, metadataId(ocflObjectId, dsId2));
661    }
662
663    @Test
664    public void processObjectMultipleVersionsPlainFormat() throws IOException {
665        final var handler = createHandler(MigrationType.PLAIN_OCFL, false, false);
666
667        final var pid = "obj2";
668        final var dsId1 = "ds3";
669        final var dsId2 = "ds4";
670
671        final var ds1V1 = datastreamVersion(dsId1, true, MANAGED, "application/xml", "<h1>hello</h1>", null);
672        final var ds2V1 = datastreamVersion(dsId2, true, MANAGED, "text/plain", "goodbye", null);
673
674        final var ds2V2 = datastreamVersion(dsId2, false, MANAGED, "text/plain", "fedora", null);
675
676        handler.processObjectVersions(List.of(
677                objectVersionReference(pid, true, List.of(ds1V1, ds2V1)),
678                objectVersionReference(pid, false, List.of(ds2V2))
679        ), new DefaultObjectInfo(pid, pid, Files.createTempFile(tempDir.getRoot().toPath(), "foxml", "xml")));
680
681        final var rootResourceId = addPrefix(pid);
682
683        verifyFcrepoNotExists(rootResourceId);
684
685        verifyObjectRdf(rawContentToString(rootResourceId, PersistencePaths.rdfResource(rootResourceId, rootResourceId)
686                .getContentFilePath()));
687
688        verifyBinary(rawContentVersionToString(rootResourceId,
689                PersistencePaths.nonRdfResource(rootResourceId, resourceId(rootResourceId, dsId1)).getContentFilePath(),
690                "v1"), ds1V1);
691        verifyPlainDescRdf(rawContentVersionToString(rootResourceId,
692                PersistencePaths.rdfResource(rootResourceId, metadataId(rootResourceId, dsId1)).getContentFilePath(),
693                "v1"), ds1V1);
694
695        verifyBinary(rawContentVersionToString(rootResourceId,
696                PersistencePaths.nonRdfResource(rootResourceId, resourceId(rootResourceId, dsId2)).getContentFilePath(),
697                "v1"), ds2V1);
698        verifyPlainDescRdf(rawContentVersionToString(rootResourceId,
699                PersistencePaths.rdfResource(rootResourceId, metadataId(rootResourceId, dsId2)).getContentFilePath(),
700                "v1"), ds2V1);
701
702        verifyBinary(rawContentVersionToString(rootResourceId,
703                PersistencePaths.nonRdfResource(rootResourceId, resourceId(rootResourceId, dsId2)).getContentFilePath(),
704                "v2"), ds2V2);
705        verifyPlainDescRdf(rawContentVersionToString(rootResourceId,
706                PersistencePaths.rdfResource(rootResourceId, metadataId(rootResourceId, dsId2)).getContentFilePath(),
707                "v2"), ds2V2);
708    }
709
710    @Test
711    public void processObjectMultipleVersionsWithDeletedDsPlainFormat() throws IOException {
712        final var handler = createHandler(MigrationType.PLAIN_OCFL, false, false);
713
714        final var pid = "obj2";
715        final var dsId1 = "ds3";
716        final var dsId2 = "ds4";
717
718        final var ds1V1 = datastreamVersion(dsId1, true, MANAGED, "application/xml", "<h1>hello</h1>", null);
719        final var ds2V1 = datastreamVersion(dsId2, true, MANAGED, "text/plain", "goodbye",
720                DS_DELETED, null, dsId2 + "-label");
721
722        final var ds2V2 = datastreamVersion(dsId2, false, MANAGED, "text/plain", "fedora",
723                DS_DELETED, null, dsId2 + "-label");
724
725        handler.processObjectVersions(List.of(
726                objectVersionReference(pid, true, List.of(ds1V1, ds2V1)),
727                objectVersionReference(pid, false, List.of(ds2V2))
728        ), new DefaultObjectInfo(pid, pid, Files.createTempFile(tempDir.getRoot().toPath(), "foxml", "xml")));
729
730        final var rootResourceId = addPrefix(pid);
731
732        verifyFcrepoNotExists(rootResourceId);
733
734        verifyObjectRdf(rawContentToString(rootResourceId, PersistencePaths.rdfResource(rootResourceId, rootResourceId)
735                .getContentFilePath()));
736
737        verifyBinary(rawContentVersionToString(rootResourceId,
738                PersistencePaths.nonRdfResource(rootResourceId, resourceId(rootResourceId, dsId1)).getContentFilePath(),
739                "v1"), ds1V1);
740        verifyPlainDescRdf(rawContentVersionToString(rootResourceId,
741                PersistencePaths.rdfResource(rootResourceId, metadataId(rootResourceId, dsId1)).getContentFilePath(),
742                "v1"), ds1V1);
743
744        verifyBinary(rawContentVersionToString(rootResourceId,
745                PersistencePaths.nonRdfResource(rootResourceId, resourceId(rootResourceId, dsId2)).getContentFilePath(),
746                "v1"), ds2V1);
747        verifyPlainDescRdf(rawContentVersionToString(rootResourceId,
748                PersistencePaths.rdfResource(rootResourceId, metadataId(rootResourceId, dsId2)).getContentFilePath(),
749                "v1"), ds2V1);
750
751        verifyBinary(rawContentVersionToString(rootResourceId,
752                PersistencePaths.nonRdfResource(rootResourceId, resourceId(rootResourceId, dsId2)).getContentFilePath(),
753                "v2"), ds2V2);
754        verifyPlainDescRdf(rawContentVersionToString(rootResourceId,
755                PersistencePaths.rdfResource(rootResourceId, metadataId(rootResourceId, dsId2)).getContentFilePath(),
756                "v2"), ds2V2);
757
758        rawVerifyDoesNotExist(rootResourceId,
759                PersistencePaths.nonRdfResource(rootResourceId, resourceId(rootResourceId, dsId2))
760                        .getContentFilePath());
761        rawVerifyDoesNotExist(rootResourceId,
762                PersistencePaths.rdfResource(rootResourceId, metadataId(rootResourceId, dsId2)).getContentFilePath());
763    }
764
765    @Test
766    public void processObjectMultipleVersionsWithInactiveDeletedObjectPlainFormat() throws IOException {
767        final var handler = createHandler(MigrationType.PLAIN_OCFL, false, true);
768
769        final var pid = "obj2";
770        final var dsId1 = "ds3";
771        final var dsId2 = "ds4";
772
773        final var ds1V1 = datastreamVersion(dsId1, true, MANAGED, "application/xml", "<h1>hello</h1>", null);
774        final var ds2V1 = datastreamVersion(dsId2, true, MANAGED, "text/plain", "goodbye", null);
775
776        final var ds2V2 = datastreamVersion(dsId2, false, MANAGED, "text/plain", "fedora", null);
777
778        handler.processObjectVersions(List.of(
779                objectVersionReference(pid, true, OBJ_INACTIVE, List.of(ds1V1, ds2V1)),
780                objectVersionReference(pid, false, OBJ_INACTIVE, List.of(ds2V2))
781        ), new DefaultObjectInfo(pid, pid, Files.createTempFile(tempDir.getRoot().toPath(), "foxml", "xml")));
782
783        final var rootResourceId = addPrefix(pid);
784
785        verifyFcrepoNotExists(rootResourceId);
786
787        verifyObjectRdf(rawContentVersionToString(rootResourceId,
788                PersistencePaths.rdfResource(rootResourceId, rootResourceId).getContentFilePath(), "v1"));
789
790        verifyBinary(rawContentVersionToString(rootResourceId,
791                PersistencePaths.nonRdfResource(rootResourceId, resourceId(rootResourceId, dsId1)).getContentFilePath(),
792                "v1"), ds1V1);
793        verifyPlainDescRdf(rawContentVersionToString(rootResourceId,
794                PersistencePaths.rdfResource(rootResourceId, metadataId(rootResourceId, dsId1)).getContentFilePath(),
795                "v1"), ds1V1);
796
797        verifyBinary(rawContentVersionToString(rootResourceId,
798                PersistencePaths.nonRdfResource(rootResourceId, resourceId(rootResourceId, dsId2)).getContentFilePath(),
799                "v1"), ds2V1);
800        verifyPlainDescRdf(rawContentVersionToString(rootResourceId,
801                PersistencePaths.rdfResource(rootResourceId, metadataId(rootResourceId, dsId2)).getContentFilePath(),
802                "v1"), ds2V1);
803
804        verifyBinary(rawContentVersionToString(rootResourceId,
805                PersistencePaths.nonRdfResource(rootResourceId, resourceId(rootResourceId, dsId2)).getContentFilePath(),
806                "v2"), ds2V2);
807        verifyPlainDescRdf(rawContentVersionToString(rootResourceId,
808                PersistencePaths.rdfResource(rootResourceId, metadataId(rootResourceId, dsId2)).getContentFilePath(),
809                "v2"), ds2V2);
810
811        rawVerifyDoesNotExist(rootResourceId, PersistencePaths.rdfResource(rootResourceId, rootResourceId)
812                .getContentFilePath());
813
814        rawVerifyDoesNotExist(rootResourceId,
815                PersistencePaths.nonRdfResource(rootResourceId, resourceId(rootResourceId, dsId1))
816                        .getContentFilePath());
817        rawVerifyDoesNotExist(rootResourceId,
818                PersistencePaths.rdfResource(rootResourceId, metadataId(rootResourceId, dsId1)).getContentFilePath());
819
820        rawVerifyDoesNotExist(rootResourceId,
821                PersistencePaths.nonRdfResource(rootResourceId, resourceId(rootResourceId, dsId2))
822                        .getContentFilePath());
823        rawVerifyDoesNotExist(rootResourceId,
824                PersistencePaths.rdfResource(rootResourceId, metadataId(rootResourceId, dsId2)).getContentFilePath());
825    }
826
827    @Test
828    public void processObjectSingleVersionF6FormatWithExtensions() throws IOException {
829        final var handler = createHandler(MigrationType.FEDORA_OCFL, true, false);
830
831        final var pid = "obj1";
832        final var dsId1 = "ds1";
833        final var dsId2 = "ds2";
834        final var dsId3 = "ds3";
835
836        final var ds1 = datastreamVersion(dsId1, true, MANAGED, "text/plain", "text", null);
837        final var ds2 = datastreamVersion(dsId2, true, MANAGED, "application/rdf+xml", "xml", null);
838        final var ds3 = datastreamVersion(dsId3, true, MANAGED, "image/jpeg", "image", null);
839
840        handler.processObjectVersions(List.of(
841                objectVersionReference(pid, true, List.of(ds1, ds2, ds3))
842        ), new DefaultObjectInfo(pid, pid, Files.createTempFile(tempDir.getRoot().toPath(), "foxml", "xml")));
843
844        final var ocflObjectId = addPrefix(pid);
845        final var session = sessionFactory.newSession(ocflObjectId);
846
847        verifyObjectRdf(contentToString(session, ocflObjectId));
848        verifyObjectHeaders(session, ocflObjectId);
849
850        verifyBinary(contentToString(session, ocflObjectId, dsId1), ds1);
851        verifyHeaders(session, ocflObjectId, dsId1, ds1, "v1", "ds1-label.txt");
852        verifyDescRdf(session, ocflObjectId, dsId1, ds1);
853        verifyDescHeaders(session, ocflObjectId, dsId1);
854
855        verifyBinary(contentToString(session, ocflObjectId, dsId2), ds2);
856        verifyHeaders(session, ocflObjectId, dsId2, ds2, "v1", "ds2-label.rdf");
857        verifyDescRdf(session, ocflObjectId, dsId2, ds2);
858        verifyDescHeaders(session, ocflObjectId, dsId2);
859
860        verifyBinary(contentToString(session, ocflObjectId, dsId3), ds3);
861        verifyHeaders(session, ocflObjectId, dsId3, ds3, "v1", "ds3-label.jpg");
862        verifyDescRdf(session, ocflObjectId, dsId3, ds3);
863        verifyDescHeaders(session, ocflObjectId, dsId3);
864    }
865
866    @Test
867    public void processObjectSingleVersionPlainFormatWithExtensions() throws IOException {
868        final var handler = createHandler(MigrationType.PLAIN_OCFL, true, false);
869
870        final var pid = "obj1";
871        final var dsId1 = "ds1";
872        final var dsId2 = "ds2";
873        final var dsId3 = "ds3";
874
875        final var ds1 = datastreamVersion(dsId1, true, MANAGED, "text/plain", "text", null);
876        final var ds2 = datastreamVersion(dsId2, true, MANAGED, "application/rdf+xml", "xml", null);
877        final var ds3 = datastreamVersion(dsId3, true, MANAGED, "image/jpeg", "image", null);
878
879        handler.processObjectVersions(List.of(
880                objectVersionReference(pid, true, List.of(ds1, ds2, ds3))
881        ), new DefaultObjectInfo(pid, pid, Files.createTempFile(tempDir.getRoot().toPath(), "foxml", "xml")));
882
883        final var rootResourceId = addPrefix(pid);
884
885        verifyFcrepoNotExists(rootResourceId);
886
887        verifyObjectRdf(rawContentToString(rootResourceId, PersistencePaths.rdfResource(rootResourceId, rootResourceId)
888                .getContentFilePath()));
889
890        verifyBinary(rawContentToString(rootResourceId,
891                PersistencePaths.nonRdfResource(rootResourceId, resourceId(rootResourceId, dsId1))
892                        .getContentFilePath()),
893                ds1);
894        verifyPlainDescRdf(rawContentToString(rootResourceId,
895                PersistencePaths.rdfResource(rootResourceId, metadataId(rootResourceId, dsId1))
896                        .getContentFilePath()),
897                ds1);
898
899        verifyBinary(rawContentToString(rootResourceId,
900                PersistencePaths.nonRdfResource(rootResourceId, resourceId(rootResourceId, dsId2))
901                        .getContentFilePath()),
902                ds2);
903        verifyPlainDescRdf(rawContentToString(rootResourceId,
904                PersistencePaths.rdfResource(rootResourceId, metadataId(rootResourceId, dsId2))
905                        .getContentFilePath()),
906                ds2);
907
908        verifyBinary(rawContentToString(rootResourceId,
909                PersistencePaths.nonRdfResource(rootResourceId, resourceId(rootResourceId, dsId3))
910                        .getContentFilePath()),
911                ds3);
912        verifyPlainDescRdf(rawContentToString(rootResourceId,
913                PersistencePaths.rdfResource(rootResourceId, metadataId(rootResourceId, dsId3))
914                        .getContentFilePath()),
915                ds3);
916    }
917
918    @Test
919    public void processObjectSingleVersionF6FormatWithExternalBinary() throws IOException {
920        final var handler = createHandler(MigrationType.FEDORA_OCFL, false, false);
921
922        final var pid = "obj1";
923        final var dsId1 = "ds1";
924        final var dsId2 = "ds2";
925        final var dsId3 = "ds3";
926
927        final var ds1 = datastreamVersion(dsId1, true, MANAGED, "text/plain", "hello", null);
928        final var ds2 = datastreamVersion(dsId2, true, PROXY, "text/plain", "", "https://external");
929        final var ds3 = datastreamVersion(dsId3, true, REDIRECT, "text/plain", "", "https://redirect");
930
931        handler.processObjectVersions(List.of(
932                objectVersionReference(pid, true, List.of(ds1, ds2, ds3))
933        ), new DefaultObjectInfo(pid, pid, Files.createTempFile(tempDir.getRoot().toPath(), "foxml", "xml")));
934
935        final var ocflObjectId = addPrefix(pid);
936        final var session = sessionFactory.newSession(ocflObjectId);
937
938        verifyObjectRdf(contentToString(session, ocflObjectId));
939        verifyObjectHeaders(session, ocflObjectId);
940
941        verifyBinary(contentToString(session, ocflObjectId, dsId1), ds1);
942        verifyHeaders(session, ocflObjectId, dsId1, ds1);
943        verifyDescRdf(session, ocflObjectId, dsId1, ds1);
944        verifyDescHeaders(session, ocflObjectId, dsId1);
945
946        verifyContentNotExists(session, resourceId(ocflObjectId, dsId2));
947        verifyHeaders(session, ocflObjectId, dsId2, ds2);
948        verifyDescRdf(session, ocflObjectId, dsId2, ds2);
949        verifyDescHeaders(session, ocflObjectId, dsId2);
950
951        verifyContentNotExists(session, resourceId(ocflObjectId, dsId3));
952        verifyHeaders(session, ocflObjectId, dsId3, ds3);
953        verifyDescRdf(session, ocflObjectId, dsId3, ds3);
954        verifyDescHeaders(session, ocflObjectId, dsId3);
955    }
956
957    @Test
958    public void processObjectSingleVersionPlainFormatWithExternalBinary() throws IOException {
959        final var handler = createHandler(MigrationType.PLAIN_OCFL, false, false);
960
961        final var pid = "obj1";
962        final var dsId1 = "ds1";
963        final var dsId2 = "ds2";
964        final var dsId3 = "ds3";
965
966        final var ds1 = datastreamVersion(dsId1, true, MANAGED, "text/plain", "hello", null);
967        final var ds2 = datastreamVersion(dsId2, true, PROXY, "text/plain", "", "https://external");
968        final var ds3 = datastreamVersion(dsId3, true, REDIRECT, "text/plain", "", "https://redirect");
969
970        handler.processObjectVersions(List.of(
971                objectVersionReference(pid, true, List.of(ds1, ds2, ds3))
972        ), new DefaultObjectInfo(pid, pid, Files.createTempFile(tempDir.getRoot().toPath(), "foxml", "xml")));
973
974        final var rootResourceId = addPrefix(pid);
975
976        verifyFcrepoNotExists(rootResourceId);
977
978        verifyObjectRdf(rawContentToString(rootResourceId, PersistencePaths.rdfResource(rootResourceId, rootResourceId)
979                .getContentFilePath()));
980
981        verifyBinary(rawContentToString(rootResourceId,
982                PersistencePaths.nonRdfResource(rootResourceId, resourceId(rootResourceId, dsId1))
983                        .getContentFilePath()),
984                ds1);
985        verifyPlainDescRdf(rawContentToString(rootResourceId,
986                PersistencePaths.rdfResource(rootResourceId, metadataId(rootResourceId, dsId1)).getContentFilePath()),
987                ds1);
988
989        verifyBinary(rawContentToString(rootResourceId,
990                PersistencePaths.nonRdfResource(rootResourceId, resourceId(rootResourceId, dsId2))
991                        .getContentFilePath()),
992                ds2);
993
994        verifyBinary(rawContentToString(rootResourceId,
995                PersistencePaths.nonRdfResource(rootResourceId, resourceId(rootResourceId, dsId3))
996                        .getContentFilePath()),
997                ds3);
998    }
999
1000    private void verifyBinary(final String content, final DatastreamVersion datastreamVersion) {
1001        try {
1002            if ("RE".contains(datastreamVersion.getDatastreamInfo().getControlGroup())) {
1003                assertEquals(datastreamVersion.getExternalOrRedirectURL(), content);
1004            } else {
1005                assertEquals(IOUtils.toString(datastreamVersion.getContent()), content);
1006            }
1007        } catch (IOException e) {
1008            throw new UncheckedIOException(e);
1009        }
1010    }
1011
1012    private void verifyHeaders(final OcflObjectSession session,
1013                               final String ocflObjectId,
1014                               final String dsId,
1015                               final DatastreamVersion datastreamVersion) {
1016        verifyHeaders(session, ocflObjectId, dsId, datastreamVersion, null);
1017    }
1018
1019    private void verifyHeaders(final OcflObjectSession session,
1020                               final String ocflObjectId,
1021                               final String dsId,
1022                               final DatastreamVersion datastreamVersion,
1023                               final String versionNumber) {
1024        verifyHeaders(session, ocflObjectId, dsId, datastreamVersion, versionNumber, dsId + "-label");
1025    }
1026
1027    private void verifyHeaders(final OcflObjectSession session,
1028                               final String ocflObjectId,
1029                               final String dsId,
1030                               final DatastreamVersion datastreamVersion,
1031                               final String versionNumber,
1032                               final String filename) {
1033        final var resourceId = resourceId(ocflObjectId, dsId);
1034        try (final var content = session.readContent(resourceId, versionNumber)) {
1035            final var headers = content.getHeaders();
1036            assertEquals(ResourceHeadersVersion.V1_0, headers.getHeadersVersion());
1037            assertEquals(resourceId, headers.getId());
1038            assertEquals(ocflObjectId, headers.getParent());
1039            if (ResourceMigrationType.ARCHIVAL == resourceMigrationType) {
1040                assertEquals(ocflObjectId, headers.getArchivalGroupId());
1041                assertFalse("not root", headers.isObjectRoot());
1042            } else {
1043                assertNull("no AG", headers.getArchivalGroupId());
1044                assertTrue("is root", headers.isObjectRoot());
1045            }
1046            assertEquals(InteractionModel.NON_RDF.getUri(), headers.getInteractionModel());
1047            assertFalse("not AG", headers.isArchivalGroup());
1048            assertFalse("not deleted", headers.isDeleted());
1049            assertEquals(USER, headers.getCreatedBy());
1050            assertEquals(USER, headers.getLastModifiedBy());
1051            assertThat(headers.getLastModifiedDate().toString(), containsString(date));
1052            assertThat(headers.getMementoCreatedDate().toString(), containsString(date));
1053            assertThat(headers.getCreatedDate().toString(), containsString(date));
1054            if (INLINE.equals(datastreamVersion.getDatastreamInfo().getControlGroup())) {
1055                assertNotEquals(datastreamVersion.getSize(), headers.getContentSize());
1056            } else {
1057                assertEquals(datastreamVersion.getSize(), headers.getContentSize());
1058            }
1059            assertEquals(datastreamVersion.getMimeType(), headers.getMimeType());
1060            assertEquals(filename, headers.getFilename());
1061            assertEquals(DigestUtils.md5Hex(
1062                    String.valueOf(Instant.parse(datastreamVersion.getCreated()).toEpochMilli())).toUpperCase(),
1063                    headers.getStateToken());
1064            if (headers.getExternalHandling() != null) {
1065                assertEquals(1, headers.getDigests().size());
1066            } else {
1067                assertEquals(2, headers.getDigests().size());
1068            }
1069            assertEquals(datastreamVersion.getExternalOrRedirectURL(), headers.getExternalUrl());
1070            if (Objects.equals(REDIRECT, datastreamVersion.getDatastreamInfo().getControlGroup())) {
1071                assertEquals("redirect", headers.getExternalHandling());
1072            } else if (Objects.equals(PROXY, datastreamVersion.getDatastreamInfo().getControlGroup())) {
1073                assertEquals("proxy", headers.getExternalHandling());
1074            }
1075        } catch (Exception e) {
1076            throw new RuntimeException(e);
1077        }
1078    }
1079
1080    private void verifyDescHeaders(final OcflObjectSession session,
1081                                   final String ocflObjectId,
1082                                   final String dsId) {
1083        verifyDescHeaders(session, ocflObjectId, dsId, null);
1084    }
1085
1086    private void verifyDescHeaders(final OcflObjectSession session,
1087                                   final String ocflObjectId,
1088                                   final String dsId,
1089                                   final String versionNumber) {
1090        try (final var content = session.readContent(metadataId(ocflObjectId, dsId), versionNumber)) {
1091            final var headers = content.getHeaders();
1092            assertEquals(ResourceHeadersVersion.V1_0, headers.getHeadersVersion());
1093            assertEquals(metadataId(ocflObjectId, dsId), headers.getId());
1094            assertEquals(resourceId(ocflObjectId, dsId), headers.getParent());
1095            if (ResourceMigrationType.ARCHIVAL == resourceMigrationType) {
1096                assertEquals(ocflObjectId, headers.getArchivalGroupId());
1097            } else {
1098                assertNull("no AG", headers.getArchivalGroupId());
1099            }
1100            assertEquals(InteractionModel.NON_RDF_DESCRIPTION.getUri(), headers.getInteractionModel());
1101            assertFalse("not AG", headers.isArchivalGroup());
1102            assertFalse("not root", headers.isObjectRoot());
1103            assertFalse("not deleted", headers.isDeleted());
1104            assertEquals(USER, headers.getCreatedBy());
1105            assertEquals(USER, headers.getLastModifiedBy());
1106            assertThat(headers.getLastModifiedDate().toString(), containsString(date));
1107            assertThat(headers.getMementoCreatedDate().toString(), containsString(date));
1108            assertThat(headers.getCreatedDate().toString(), containsString(date));
1109        } catch (Exception e) {
1110            throw new RuntimeException(e);
1111        }
1112    }
1113
1114    private void verifyObjectHeaders(final OcflObjectSession session,
1115                                     final String ocflObjectId) {
1116        verifyObjectHeaders(session, ocflObjectId, null);
1117    }
1118
1119    private void verifyObjectHeaders(final OcflObjectSession session,
1120                                     final String ocflObjectId,
1121                                     final String versionNumber) {
1122        try (final var content = session.readContent(ocflObjectId, versionNumber)) {
1123            final var headers = content.getHeaders();
1124            assertEquals(ResourceHeadersVersion.V1_0, headers.getHeadersVersion());
1125            assertEquals(ocflObjectId, headers.getId());
1126            assertEquals(FCREPO_ROOT, headers.getParent());
1127            assertEquals(InteractionModel.BASIC_CONTAINER.getUri(), headers.getInteractionModel());
1128            assertEquals("is AG", ResourceMigrationType.ARCHIVAL == resourceMigrationType,
1129                    headers.isArchivalGroup());
1130            assertTrue("is root", headers.isObjectRoot());
1131            assertFalse("not deleted", headers.isDeleted());
1132            assertEquals(USER, headers.getCreatedBy());
1133            assertEquals(USER, headers.getLastModifiedBy());
1134            assertThat(headers.getLastModifiedDate().toString(), containsString(date));
1135            assertThat(headers.getMementoCreatedDate().toString(), containsString(date));
1136            assertThat(headers.getCreatedDate().toString(), containsString(date));
1137        } catch (Exception e) {
1138            throw new RuntimeException(e);
1139        }
1140    }
1141
1142    private String verifyDescRdf(final OcflObjectSession session,
1143                               final String ocflObjectId,
1144                               final String dsId,
1145                               final DatastreamVersion datastreamVersion) {
1146        return verifyDescRdf(session, ocflObjectId, dsId, datastreamVersion, null);
1147    }
1148
1149    private String verifyDescRdf(final OcflObjectSession session,
1150                               final String ocflObjectId,
1151                               final String dsId,
1152                               final DatastreamVersion datastreamVersion,
1153                               final String versionNumber) {
1154        try (final var content = session.readContent(metadataId(ocflObjectId, dsId), versionNumber)) {
1155            final var value = IOUtils.toString(content.getContentStream().get());
1156            assertThat(value, allOf(
1157                    containsString(datastreamVersion.getLabel()),
1158                    containsString(datastreamVersion.getFormatUri()),
1159                    containsString("objState")
1160            ));
1161            return value;
1162        } catch (Exception e) {
1163            throw new RuntimeException(e);
1164        }
1165    }
1166
1167    private void verifyPlainDescRdf(final String content,
1168                                    final DatastreamVersion datastreamVersion) {
1169        assertThat(content, allOf(
1170                containsString(datastreamVersion.getLabel()),
1171                containsString(datastreamVersion.getFormatUri()),
1172                containsString(datastreamVersion.getMimeType()),
1173                containsString(datastreamVersion.getDatastreamInfo().getDatastreamId()),
1174                containsString("objState"),
1175                containsString("hasMessageDigest"),
1176                containsString("lastModified"),
1177                containsString(date),
1178                containsString("created")));
1179    }
1180
1181    private void verifyObjectRdf(final String content) {
1182        assertThat(content, allOf(
1183                containsString("lastModifiedDate"),
1184                containsString(date),
1185                containsString("createdDate")
1186        ));
1187    }
1188
1189    private String contentToString(final OcflObjectSession session, final String ocflObjectId) {
1190        return contentVersionToString(session, ocflObjectId, null);
1191    }
1192
1193    private String contentVersionToString(final OcflObjectSession session,
1194                                          final String ocflObjectId,
1195                                          final String versionNumber) {
1196        try (final var content = session.readContent(ocflObjectId, versionNumber)) {
1197            return IOUtils.toString(content.getContentStream().get());
1198        } catch (IOException e) {
1199            throw new UncheckedIOException(e);
1200        }
1201    }
1202
1203    private String contentToString(final OcflObjectSession session, final String ocflObjectId, final String dsId) {
1204        return contentVersionToString(session, ocflObjectId, dsId, null);
1205    }
1206
1207    private String contentVersionToString(final OcflObjectSession session,
1208                                          final String ocflObjectId,
1209                                          final String dsId,
1210                                          final String versionNumber) {
1211        try (final var content = session.readContent(resourceId(ocflObjectId, dsId), versionNumber)) {
1212            return IOUtils.toString(content.getContentStream().get());
1213        } catch (IOException e) {
1214            throw new UncheckedIOException(e);
1215        }
1216    }
1217
1218    private String rawContentToString(final String ocflObjectId, final String path) {
1219        try (final var content = ocflRepo.getObject(ObjectVersionId.head(ocflObjectId))
1220                .getFile(path).getStream()) {
1221            return IOUtils.toString(content);
1222        } catch (IOException e) {
1223            throw new UncheckedIOException(e);
1224        }
1225    }
1226
1227    private String rawContentVersionToString(final String ocflObjectId, final String path, final String versionNumber) {
1228        try (final var content = ocflRepo.getObject(
1229                ObjectVersionId.version(ocflObjectId, versionNumber)).getFile(path).getStream()) {
1230            return IOUtils.toString(content);
1231        } catch (IOException e) {
1232            throw new UncheckedIOException(e);
1233        }
1234    }
1235
1236    private void rawVerifyDoesNotExist(final String ocflObjectId, final String path) {
1237        assertFalse(String.format("object %s not contain path %s", ocflObjectId, path),
1238                ocflRepo.describeVersion(ObjectVersionId.head(ocflObjectId)).containsFile(path));
1239    }
1240
1241    private ArchiveGroupHandler createHandler(final MigrationType migrationType,
1242                                              final boolean addExtensions,
1243                                              final boolean deleteInactive) {
1244        if (migrationType == MigrationType.PLAIN_OCFL) {
1245            return new ArchiveGroupHandler(plainSessionFactory, migrationType, resourceMigrationType,
1246                    addExtensions, deleteInactive,
1247                    false, USER,"info:fedora/", false);
1248        } else {
1249            return new ArchiveGroupHandler(sessionFactory, migrationType, resourceMigrationType,
1250                    addExtensions, deleteInactive, false, USER,
1251                    "info:fedora/", false);
1252        }
1253    }
1254
1255    private ObjectVersionReference objectVersionReference(final String pid,
1256                                                          final boolean isFirst,
1257                                                          final List<DatastreamVersion> datastreamVersions)
1258            throws IOException {
1259        return objectVersionReference(pid, isFirst, OBJ_ACTIVE, datastreamVersions);
1260    }
1261
1262    private ObjectVersionReference objectVersionReference(final String pid,
1263                                                          final boolean isFirst,
1264                                                          final String state,
1265                                                          final List<DatastreamVersion> datastreamVersions)
1266            throws IOException {
1267        final var mock = Mockito.mock(ObjectVersionReference.class);
1268        when(mock.isFirstVersion()).thenReturn(isFirst);
1269        if (isFirst) {
1270            final var properties = objectProperties(List.of(
1271                    objectProperty("info:fedora/fedora-system:def/view#lastModifiedDate", Instant.now().toString()),
1272                    objectProperty("info:fedora/fedora-system:def/model#createdDate", Instant.now().toString()),
1273                    objectProperty("info:fedora/fedora-system:def/model#state", state)
1274            ));
1275            when(mock.getObjectProperties()).thenReturn(properties);
1276        }
1277        when(mock.getObject()).thenReturn(null);
1278        when(mock.listChangedDatastreams()).thenReturn(datastreamVersions);
1279        when(mock.getVersionDate()).thenReturn(Instant.now().toString());
1280        return mock;
1281    }
1282
1283    private ObjectProperties objectProperties(final List<? extends ObjectProperty> properties) {
1284        final var mock = Mockito.mock(ObjectProperties.class);
1285        doReturn(properties).when(mock).listProperties();
1286        return mock;
1287    }
1288
1289    private ObjectProperty objectProperty(final String name, final String value) {
1290        final var mock = Mockito.mock(ObjectProperty.class);
1291        when(mock.getName()).thenReturn(name);
1292        when(mock.getValue()).thenReturn(value);
1293        return mock;
1294    }
1295
1296    private DatastreamVersion datastreamVersion(final String datastreamId,
1297                                                final boolean isFirst,
1298                                                final String controlGroup,
1299                                                final String mimeType,
1300                                                final String content,
1301                                                final String externalUrl) {
1302        return datastreamVersion(datastreamId, isFirst, controlGroup, mimeType,
1303                content, DS_ACTIVE, externalUrl, datastreamId + "-label");
1304    }
1305
1306    private DatastreamVersion datastreamVersion(final String datastreamId,
1307                                                final boolean isFirst,
1308                                                final String controlGroup,
1309                                                final String mimeType,
1310                                                final String content,
1311                                                final String state,
1312                                                final String externalUrl,
1313                                                final String label) {
1314        final var mock = Mockito.mock(DatastreamVersion.class);
1315        final var info = datastreamInfo(datastreamId, controlGroup, state);
1316        when(mock.getDatastreamInfo()).thenReturn(info);
1317        when(mock.getMimeType()).thenReturn(mimeType);
1318        try {
1319            when(mock.getContent()).thenAnswer((Answer<InputStream>) invocation -> {
1320                return new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8));
1321            });
1322        } catch (IOException e) {
1323            throw new UncheckedIOException(e);
1324        }
1325        when(mock.isFirstVersionIn(Mockito.isNull())).thenReturn(isFirst);
1326        when(mock.getCreated()).thenReturn(Instant.now().toString());
1327        when(mock.getExternalOrRedirectURL()).thenReturn(externalUrl);
1328        when(mock.getSize()).thenReturn((long) content.length());
1329        final var contentDigest = contentDigest(content);
1330        when(mock.getContentDigest()).thenReturn(contentDigest);
1331        when(mock.getLabel()).thenReturn(label);
1332        when(mock.getFormatUri()).thenReturn("http://format-id");
1333        return mock;
1334    }
1335
1336    private DatastreamInfo datastreamInfo(final String datastreamId, final String controlGroup, final String state) {
1337        final var mock = Mockito.mock(DatastreamInfo.class);
1338        when(mock.getDatastreamId()).thenReturn(datastreamId);
1339        when(mock.getControlGroup()).thenReturn(controlGroup);
1340        when(mock.getState()).thenReturn(state);
1341        return mock;
1342    }
1343
1344    private ContentDigest contentDigest(final String content) {
1345        final var mock = Mockito.mock(ContentDigest.class);
1346        when(mock.getType()).thenReturn("md5");
1347        when(mock.getDigest()).thenReturn(DigestUtils.md5Hex(content));
1348        return mock;
1349    }
1350
1351    private String addPrefix(final String pid) {
1352        return FCREPO_ROOT + pid;
1353    }
1354
1355    private String resourceId(final String ocflObjectId, final String dsId) {
1356        return ocflObjectId + "/" + dsId;
1357    }
1358
1359    private String metadataId(final String ocflObjectId, final String dsId) {
1360        return resourceId(ocflObjectId, dsId) + "/fcr:metadata";
1361    }
1362
1363    private void verifyFcrepoNotExists(final String ocflObjectId) {
1364        final var count = ocflRepo.describeVersion(ObjectVersionId.head(ocflObjectId)).getFiles().stream()
1365                .map(FileDetails::getPath)
1366                .filter(file -> file.startsWith(".fcrepo/"))
1367                .count();
1368        assertEquals(0, count);
1369    }
1370
1371    private void verifyContentNotExists(final OcflObjectSession session, final String ocflObjectId) {
1372        try (final var content = session.readContent(ocflObjectId)) {
1373            assertTrue("Content should not exist", content.getContentStream().isEmpty());
1374        } catch (IOException e) {
1375            throw new UncheckedIOException(e);
1376        }
1377    }
1378
1379    private void verifyResourceDeleted(final OcflObjectSession session, final String ocflObjectId) {
1380        final var headers = session.readHeaders(ocflObjectId);
1381        assertTrue("resource " + ocflObjectId + " should be deleted", headers.isDeleted());
1382        verifyContentNotExists(session, ocflObjectId);
1383    }
1384
1385}