feat(storage): Implemented StorageService

- Implemented methods for storing and loading objects and streams in StorageServiceImpl.
- Added support for saving and loading streams using InputStream and OutputStream.
- Enhanced ObjectSerialization to support serialization directly to OutputStream.
- Updated StorageIndex to support recursive listing of items.
- Added comprehensive unit tests for new functionalities in StorageServiceImpl and ObjectSerialization.
- Improved error handling and logging for storage operations.
- Updated AutoConfiguration to include StorageServiceAutoConfiguration.
This commit is contained in:
extio-de
2025-12-22 15:36:53 +01:00
parent 65d3775a04
commit b5cd3fa1fe
10 changed files with 1026 additions and 32 deletions

View File

@@ -24,7 +24,7 @@ public class StorageIndexTest {
}
@Test
void listReturnsItemsOnlyAtThatDirectory() {
void listNonRecursiveReturnsItemsOnlyAtThatDirectory() {
final StorageIndex index = new StorageIndex();
final StorageItemDescriptor rootItem = new StorageItemDescriptor(UUID.randomUUID(), "root", List.of());
final StorageItemDescriptor aItem = new StorageItemDescriptor(UUID.randomUUID(), "a1", List.of("a"));
@@ -32,10 +32,10 @@ public class StorageIndexTest {
index.add(rootItem);
index.add(aItem);
index.add(abItem);
assertThat(index.list(List.of())).containsExactlyInAnyOrder(rootItem);
assertThat(index.list(List.of("a"))).containsExactlyInAnyOrder(aItem);
assertThat(index.list(List.of("a", "b"))).containsExactlyInAnyOrder(abItem);
assertThat(index.list(List.of("missing"))).isEmpty();
assertThat(index.list(List.of(), false)).containsExactlyInAnyOrder(rootItem);
assertThat(index.list(List.of("a"), false)).containsExactlyInAnyOrder(aItem);
assertThat(index.list(List.of("a", "b"), false)).containsExactlyInAnyOrder(abItem);
assertThat(index.list(List.of("missing"), false)).isEmpty();
}
@Test
@@ -45,7 +45,7 @@ public class StorageIndexTest {
index.add(descriptor);
assertThat(index.find(List.of("a", "b"), "x")).isPresent();
assertThat(index.find(List.of(" ", "A", "b "), "X")).isPresent();
assertThat(index.list(List.of("a", "b"))).hasSize(1);
assertThat(index.list(List.of("a", "b"), false)).hasSize(1);
}
@Test
@@ -90,8 +90,8 @@ public class StorageIndexTest {
assertThat(index.remove(leaf.id())).isTrue();
assertThat(index.size()).isZero();
assertThat(index.find(List.of("a", "b"), "leaf")).isEmpty();
assertThat(index.list(List.of("a", "b"))).isEmpty();
assertThat(index.list(List.of("a"))).isEmpty();
assertThat(index.list(List.of("a", "b"), false)).isEmpty();
assertThat(index.list(List.of("a"), false)).isEmpty();
}
@Test
@@ -134,4 +134,75 @@ public class StorageIndexTest {
final var matches = index.findByFilenamePattern(List.of("assets"), "glob:*.png", true);
assertThat(matches).containsExactlyInAnyOrder(inBase, inChild2);
}
@Test
void listRecursiveReturnsAllItemsInDirectoryAndSubdirectories() {
final StorageIndex index = new StorageIndex();
final StorageItemDescriptor rootItem = new StorageItemDescriptor(UUID.randomUUID(), "root", List.of());
final StorageItemDescriptor aItem = new StorageItemDescriptor(UUID.randomUUID(), "a1", List.of("a"));
final StorageItemDescriptor abItem = new StorageItemDescriptor(UUID.randomUUID(), "ab1", List.of("a", "b"));
final StorageItemDescriptor abcItem = new StorageItemDescriptor(UUID.randomUUID(), "abc1", List.of("a", "b", "c"));
final StorageItemDescriptor otherItem = new StorageItemDescriptor(UUID.randomUUID(), "other", List.of("other"));
index.add(rootItem);
index.add(aItem);
index.add(abItem);
index.add(abcItem);
index.add(otherItem);
assertThat(index.list(List.of(), true)).containsExactlyInAnyOrder(rootItem, aItem, abItem, abcItem, otherItem);
assertThat(index.list(List.of("a"), true)).containsExactlyInAnyOrder(aItem, abItem, abcItem);
assertThat(index.list(List.of("a", "b"), true)).containsExactlyInAnyOrder(abItem, abcItem);
assertThat(index.list(List.of("a", "b", "c"), true)).containsExactlyInAnyOrder(abcItem);
assertThat(index.list(List.of("missing"), true)).isEmpty();
}
@Test
void listRecursiveNonExistentPathReturnsEmpty() {
final StorageIndex index = new StorageIndex();
final StorageItemDescriptor item = new StorageItemDescriptor(UUID.randomUUID(), "file", List.of("a"));
index.add(item);
assertThat(index.list(List.of("nonexistent"), true)).isEmpty();
}
@Test
void listRecursiveAndNonRecursiveAreConsistent() {
final StorageIndex index = new StorageIndex();
final StorageItemDescriptor aItem = new StorageItemDescriptor(UUID.randomUUID(), "a1", List.of("a"));
final StorageItemDescriptor abItem = new StorageItemDescriptor(UUID.randomUUID(), "ab1", List.of("a", "b"));
index.add(aItem);
index.add(abItem);
assertThat(index.list(List.of("a"), false)).containsExactly(aItem);
assertThat(index.list(List.of("a"), true)).containsExactlyInAnyOrder(aItem, abItem);
}
@Test
void listAllReturnsAllItemsInIndex() {
final StorageIndex index = new StorageIndex();
final StorageItemDescriptor root = new StorageItemDescriptor(UUID.randomUUID(), "root", List.of());
final StorageItemDescriptor a = new StorageItemDescriptor(UUID.randomUUID(), "a", List.of("a"));
final StorageItemDescriptor ab = new StorageItemDescriptor(UUID.randomUUID(), "ab", List.of("a", "b"));
final StorageItemDescriptor other = new StorageItemDescriptor(UUID.randomUUID(), "other", List.of("x", "y"));
index.add(root);
index.add(a);
index.add(ab);
index.add(other);
assertThat(index.listAll()).containsExactlyInAnyOrder(root, a, ab, other);
}
@Test
void listAllOnEmptyIndexReturnsEmpty() {
final StorageIndex index = new StorageIndex();
assertThat(index.listAll()).isEmpty();
}
@Test
void listAllIsCopyOfInternalState() {
final StorageIndex index = new StorageIndex();
final StorageItemDescriptor item = new StorageItemDescriptor(UUID.randomUUID(), "file", List.of("a"));
index.add(item);
final var list1 = index.listAll();
index.add(new StorageItemDescriptor(UUID.randomUUID(), "file2", List.of("b")));
final var list2 = index.listAll();
assertThat(list1).hasSize(1);
assertThat(list2).hasSize(2);
}
}

View File

@@ -2,10 +2,15 @@ package de.extio.game_engine.storage;
import static org.assertj.core.api.Assertions.assertThat;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import org.junit.jupiter.api.AfterAll;
@@ -16,51 +21,468 @@ import org.junit.jupiter.api.io.TempDir;
import de.extio.game_engine.util.ObjectSerialization;
class StorageServiceImplTest {
@TempDir
static Path tempDir;
@BeforeAll
static void setUpStorageLocation() {
System.setProperty("storagelocation", tempDir.resolve("storage").toString());
}
@AfterAll
static void clearStorageLocation() {
System.clearProperty("storagelocation");
}
@Test
void loadIndexNoFileDoesNotFailAndKeepsIndexEmpty() throws Exception {
Files.deleteIfExists(StorageServiceImpl.INDEX_LOCATION);
final StorageServiceImpl service = new StorageServiceImpl();
final StorageService service = new StorageServiceImpl();
final StorageIndex index = this.getIndex(service);
assertThat(index.isEmpty()).isTrue();
assertThat(Files.exists(StorageServiceImpl.INDEX_LOCATION)).isFalse();
}
@Test
void loadIndexDeserializesIndexFileAndAddsAllItems() throws Exception {
Files.deleteIfExists(StorageServiceImpl.INDEX_LOCATION);
Files.createDirectories(StorageServiceImpl.STORAGE_LOCATION);
final StorageItemDescriptor d1 = new StorageItemDescriptor(UUID.randomUUID(), "File1.txt", List.of("A"));
final StorageItemDescriptor d2 = new StorageItemDescriptor(UUID.randomUUID(), "File2.txt", List.of("A", "B"));
final StorageItemDescriptor[] descriptors = new StorageItemDescriptor[] { d1, d2 };
final byte[] bytes = ObjectSerialization.serialize(descriptors, false, false, false, new byte[0], ignored -> {
final byte[] bytes = ObjectSerialization.serialize(descriptors, true, false, false, new byte[0], ignored -> {
});
Files.write(StorageServiceImpl.INDEX_LOCATION, bytes);
final StorageServiceImpl service = new StorageServiceImpl();
final StorageService service = new StorageServiceImpl();
final StorageIndex index = this.getIndex(service);
assertThat(index.size()).isEqualTo(2);
assertThat(index.find(List.of("a"), "file1.txt")).contains(d1);
assertThat(index.find(List.of("a", "b"), "file2.txt")).contains(d2);
}
private StorageIndex getIndex(final StorageServiceImpl service) throws Exception {
@Test
void storeCreatesNewObjectAndReturnsUUID() throws Exception {
Files.deleteIfExists(StorageServiceImpl.INDEX_LOCATION);
final StorageService service = new StorageServiceImpl();
final TestData data = new TestData("test", 123);
final UUID id = service.store("myfile.dat", List.of("folder", "subfolder"), data);
assertThat(id).isNotNull();
assertThat(Files.exists(StorageServiceImpl.DATA_LOCATION.resolve(id.toString()))).isTrue();
final StorageIndex index = this.getIndex(service);
final Optional<StorageItemDescriptor> descriptor = index.find(List.of("folder", "subfolder"), "myfile.dat");
assertThat(descriptor).isPresent();
assertThat(descriptor.get().id()).isEqualTo(id);
assertThat(descriptor.get().name()).isEqualTo("myfile.dat");
assertThat(descriptor.get().path()).containsExactly("folder", "subfolder");
}
@Test
void storeStreamCreatesNewObjectAndReturnsUUID() throws Exception {
Files.deleteIfExists(StorageServiceImpl.INDEX_LOCATION);
final StorageService service = new StorageServiceImpl();
final String content = "Hello World";
final InputStream inputStream = new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8));
final UUID id = service.storeStream("mystream.txt", List.of("streams"), inputStream);
assertThat(id).isNotNull();
assertThat(Files.exists(StorageServiceImpl.DATA_LOCATION.resolve(id.toString()))).isTrue();
final StorageIndex index = this.getIndex(service);
final Optional<StorageItemDescriptor> descriptor = index.find(List.of("streams"), "mystream.txt");
assertThat(descriptor).isPresent();
assertThat(descriptor.get().id()).isEqualTo(id);
}
@Test
void loadStreamByIdReturnsInputStream() throws Exception {
Files.deleteIfExists(StorageServiceImpl.INDEX_LOCATION);
final StorageService service = new StorageServiceImpl();
final String content = "Stream Content";
final InputStream inputStream = new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8));
final UUID id = service.storeStream("mystream.txt", List.of("streams"), inputStream);
final Optional<InputStream> loaded = service.loadStreamById(id);
assertThat(loaded).isPresent();
try (InputStream in = loaded.get()) {
final String loadedContent = new String(in.readAllBytes(), StandardCharsets.UTF_8);
assertThat(loadedContent).isEqualTo(content);
}
}
@Test
void loadStreamByPathReturnsInputStream() throws Exception {
Files.deleteIfExists(StorageServiceImpl.INDEX_LOCATION);
final StorageService service = new StorageServiceImpl();
final String content = "Path Stream Content";
final InputStream inputStream = new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8));
service.storeStream("pathstream.txt", List.of("path", "streams"), inputStream);
final Optional<InputStream> loaded = service.loadStreamByPath(List.of("path", "streams"), "pathstream.txt");
assertThat(loaded).isPresent();
try (InputStream in = loaded.get()) {
final String loadedContent = new String(in.readAllBytes(), StandardCharsets.UTF_8);
assertThat(loadedContent).isEqualTo(content);
}
}
@Test
void storeStreamWithOutputStreamCreatesNewObjectAndReturnsUUIDAndOutputStream() throws Exception {
Files.deleteIfExists(StorageServiceImpl.INDEX_LOCATION);
final StorageService service = new StorageServiceImpl();
final StorageOutputStream storageOutput = service.storeStream("outstream.txt", List.of("output", "streams"));
assertThat(storageOutput.id()).isNotNull();
assertThat(storageOutput.outputStream()).isNotNull();
final String content = "Output Stream Content";
try (OutputStream out = storageOutput.outputStream()) {
out.write(content.getBytes(StandardCharsets.UTF_8));
}
assertThat(Files.exists(StorageServiceImpl.DATA_LOCATION.resolve(storageOutput.id().toString()))).isTrue();
final StorageIndex index = this.getIndex(service);
final Optional<StorageItemDescriptor> descriptor = index.find(List.of("output", "streams"), "outstream.txt");
assertThat(descriptor).isPresent();
assertThat(descriptor.get().id()).isEqualTo(storageOutput.id());
}
@Test
void storeStreamWithOutputStreamCanBeReadBack() throws Exception {
Files.deleteIfExists(StorageServiceImpl.INDEX_LOCATION);
final StorageService service = new StorageServiceImpl();
final String content = "Output Stream Readback Content";
final StorageOutputStream storageOutput = service.storeStream("readback.txt", List.of("readback"));
try (OutputStream out = storageOutput.outputStream()) {
out.write(content.getBytes(StandardCharsets.UTF_8));
}
final Optional<InputStream> loaded = service.loadStreamById(storageOutput.id());
assertThat(loaded).isPresent();
try (InputStream in = loaded.get()) {
final String loadedContent = new String(in.readAllBytes(), StandardCharsets.UTF_8);
assertThat(loadedContent).isEqualTo(content);
}
}
@Test
void storeReplacesExistingObjectWithSamePathAndName() throws Exception {
Files.deleteIfExists(StorageServiceImpl.INDEX_LOCATION);
final StorageServiceImpl service = new StorageServiceImpl();
final TestData data1 = new TestData("first", 100);
final UUID id1 = service.store("myfile.dat", List.of("folder"), data1);
final TestData data2 = new TestData("second", 200);
final UUID id2 = service.store("myfile.dat", List.of("folder"), data2);
assertThat(id1).isEqualTo(id2);
final Optional<TestData> loaded = service.loadById(TestData.class, id2);
assertThat(loaded).isPresent();
assertThat(loaded.get().value()).isEqualTo("second");
assertThat(loaded.get().number()).isEqualTo(200);
final StorageIndex index = this.getIndex(service);
assertThat(index.size()).isEqualTo(1);
}
@Test
void deleteByIdRemovesObjectAndFile() throws Exception {
Files.deleteIfExists(StorageServiceImpl.INDEX_LOCATION);
final StorageService service = new StorageServiceImpl();
final TestData data = new TestData("test", 123);
final UUID id = service.store("myfile.dat", List.of("folder"), data);
final Path filePath = StorageServiceImpl.DATA_LOCATION.resolve(id.toString());
assertThat(Files.exists(filePath)).isTrue();
final boolean deleted = service.deleteById(id);
assertThat(deleted).isTrue();
assertThat(Files.exists(filePath)).isFalse();
final StorageIndex index = this.getIndex(service);
assertThat(index.getById(id)).isEmpty();
}
@Test
void deleteByIdReturnsFalseForNonExistentId() throws Exception {
Files.deleteIfExists(StorageServiceImpl.INDEX_LOCATION);
final StorageService service = new StorageServiceImpl();
final boolean deleted = service.deleteById(UUID.randomUUID());
assertThat(deleted).isFalse();
}
@Test
void deleteByPathRemovesObject() throws Exception {
Files.deleteIfExists(StorageServiceImpl.INDEX_LOCATION);
final StorageService service = new StorageServiceImpl();
final TestData data = new TestData("test", 123);
final UUID id = service.store("myfile.dat", List.of("folder"), data);
final boolean deleted = service.deleteByPath(List.of("folder"), "myfile.dat");
assertThat(deleted).isTrue();
final StorageIndex index = this.getIndex(service);
assertThat(index.getById(id)).isEmpty();
}
@Test
void deleteByPathReturnsFalseForNonExistentPath() throws Exception {
Files.deleteIfExists(StorageServiceImpl.INDEX_LOCATION);
final StorageService service = new StorageServiceImpl();
final boolean deleted = service.deleteByPath(List.of("nonexistent"), "file.dat");
assertThat(deleted).isFalse();
}
@Test
void moveByIdUpdatesPathAndName() throws Exception {
Files.deleteIfExists(StorageServiceImpl.INDEX_LOCATION);
final StorageService service = new StorageServiceImpl();
final TestData data = new TestData("test", 123);
final UUID id = service.store("oldfile.dat", List.of("old", "path"), data);
final boolean moved = service.moveById(id, List.of("new", "path"), "newfile.dat");
assertThat(moved).isTrue();
final StorageIndex index = this.getIndex(service);
final Optional<StorageItemDescriptor> oldLocation = index.find(List.of("old", "path"), "oldfile.dat");
assertThat(oldLocation).isEmpty();
final Optional<StorageItemDescriptor> newLocation = index.find(List.of("new", "path"), "newfile.dat");
assertThat(newLocation).isPresent();
assertThat(newLocation.get().id()).isEqualTo(id);
}
@Test
void moveByIdReturnsFalseForNonExistentId() throws Exception {
Files.deleteIfExists(StorageServiceImpl.INDEX_LOCATION);
final StorageService service = new StorageServiceImpl();
final boolean moved = service.moveById(UUID.randomUUID(), List.of("new"), "file.dat");
assertThat(moved).isFalse();
}
@Test
void moveByPathUpdatesLocation() throws Exception {
Files.deleteIfExists(StorageServiceImpl.INDEX_LOCATION);
final StorageService service = new StorageServiceImpl();
final TestData data = new TestData("test", 123);
service.store("oldfile.dat", List.of("old"), data);
final boolean moved = service.moveByPath(List.of("old"), "oldfile.dat", List.of("new"), "newfile.dat");
assertThat(moved).isTrue();
final StorageIndex index = this.getIndex(service);
final Optional<StorageItemDescriptor> newLocation = index.find(List.of("new"), "newfile.dat");
assertThat(newLocation).isPresent();
}
@Test
void loadByIdDeserializesObject() throws Exception {
Files.deleteIfExists(StorageServiceImpl.INDEX_LOCATION);
final StorageServiceImpl service = new StorageServiceImpl();
final TestData data = new TestData("test", 456);
final UUID id = service.store("myfile.dat", List.of("folder"), data);
final Optional<TestData> loaded = service.loadById(TestData.class, id);
assertThat(loaded).isPresent();
assertThat(loaded.get().value()).isEqualTo("test");
assertThat(loaded.get().number()).isEqualTo(456);
}
@Test
void loadByIdReturnsEmptyForNonExistentId() throws Exception {
Files.deleteIfExists(StorageServiceImpl.INDEX_LOCATION);
final StorageServiceImpl service = new StorageServiceImpl();
final Optional<TestData> loaded = service.loadById(TestData.class, UUID.randomUUID());
assertThat(loaded).isEmpty();
}
@Test
void loadByPathDeserializesObject() throws Exception {
Files.deleteIfExists(StorageServiceImpl.INDEX_LOCATION);
final StorageServiceImpl service = new StorageServiceImpl();
final TestData data = new TestData("test", 789);
service.store("myfile.dat", List.of("folder", "sub"), data);
final Optional<TestData> loaded = service.loadByPath(TestData.class, List.of("folder", "sub"), "myfile.dat");
assertThat(loaded).isPresent();
assertThat(loaded.get().value()).isEqualTo("test");
assertThat(loaded.get().number()).isEqualTo(789);
}
@Test
void searchByIdFindsDescriptor() throws Exception {
Files.deleteIfExists(StorageServiceImpl.INDEX_LOCATION);
final StorageService service = new StorageServiceImpl();
final TestData data = new TestData("test", 123);
final UUID id = service.store("myfile.dat", List.of("folder"), data);
final Optional<StorageItemDescriptor> descriptor = service.searchById(id);
assertThat(descriptor).isPresent();
assertThat(descriptor.get().id()).isEqualTo(id);
assertThat(descriptor.get().name()).isEqualTo("myfile.dat");
}
@Test
void searchByPathFindsDescriptor() throws Exception {
Files.deleteIfExists(StorageServiceImpl.INDEX_LOCATION);
final StorageService service = new StorageServiceImpl();
final TestData data = new TestData("test", 123);
final UUID id = service.store("myfile.dat", List.of("folder"), data);
final Optional<StorageItemDescriptor> descriptor = service.searchByPath(List.of("folder"), "myfile.dat");
assertThat(descriptor).isPresent();
assertThat(descriptor.get().id()).isEqualTo(id);
}
@Test
void searchByPatternFindsMatchingFiles() throws Exception {
Files.deleteIfExists(StorageServiceImpl.INDEX_LOCATION);
final StorageService service = new StorageServiceImpl();
service.store("file1.txt", List.of("docs"), new TestData("a", 1));
service.store("file2.txt", List.of("docs"), new TestData("b", 2));
service.store("data.dat", List.of("docs"), new TestData("c", 3));
final List<StorageItemDescriptor> matches = service.searchByPattern(List.of("docs"), "*.txt", false);
assertThat(matches).hasSize(2);
assertThat(matches.stream().map(StorageItemDescriptor::name)).containsExactlyInAnyOrder("file1.txt", "file2.txt");
}
@Test
void searchByPatternRecursiveSearchesSubdirectories() throws Exception {
Files.deleteIfExists(StorageServiceImpl.INDEX_LOCATION);
final StorageService service = new StorageServiceImpl();
service.store("root.txt", List.of(), new TestData("a", 1));
service.store("sub1.txt", List.of("level1"), new TestData("b", 2));
service.store("sub2.txt", List.of("level1", "level2"), new TestData("c", 3));
final List<StorageItemDescriptor> matches = service.searchByPattern(List.of(), "*.txt", true);
assertThat(matches).hasSize(3);
}
@Test
void listPathReturnsAllItemsInPath() throws Exception {
Files.deleteIfExists(StorageServiceImpl.INDEX_LOCATION);
final StorageService service = new StorageServiceImpl();
service.store("file1.txt", List.of("docs"), new TestData("a", 1));
service.store("file2.txt", List.of("docs"), new TestData("b", 2));
service.store("other.txt", List.of("other"), new TestData("c", 3));
final List<StorageItemDescriptor> items = service.listPath(List.of("docs"), false);
assertThat(items).hasSize(2);
assertThat(items.stream().map(StorageItemDescriptor::name)).containsExactlyInAnyOrder("file1.txt", "file2.txt");
}
@Test
void listPathNonRecursiveReturnsOnlyDirectItems() throws Exception {
Files.deleteIfExists(StorageServiceImpl.INDEX_LOCATION);
final StorageService service = new StorageServiceImpl();
service.store("root.txt", List.of("docs"), new TestData("a", 1));
service.store("sub1.txt", List.of("docs", "subdir"), new TestData("b", 2));
service.store("sub2.txt", List.of("docs", "subdir", "deeper"), new TestData("c", 3));
final List<StorageItemDescriptor> items = service.listPath(List.of("docs"), false);
assertThat(items).hasSize(1);
assertThat(items.get(0).name()).isEqualTo("root.txt");
}
@Test
void listPathRecursiveReturnsAllItemsInSubdirectories() throws Exception {
Files.deleteIfExists(StorageServiceImpl.INDEX_LOCATION);
final StorageService service = new StorageServiceImpl();
service.store("root.txt", List.of("docs"), new TestData("a", 1));
service.store("sub1.txt", List.of("docs", "subdir"), new TestData("b", 2));
service.store("sub2.txt", List.of("docs", "subdir", "deeper"), new TestData("c", 3));
final List<StorageItemDescriptor> items = service.listPath(List.of("docs"), true);
assertThat(items).hasSize(3);
assertThat(items.stream().map(StorageItemDescriptor::name)).containsExactlyInAnyOrder("root.txt", "sub1.txt", "sub2.txt");
}
@Test
void listPathRecursiveFromRootReturnsAllItems() throws Exception {
Files.deleteIfExists(StorageServiceImpl.INDEX_LOCATION);
final StorageService service = new StorageServiceImpl();
service.store("file1.txt", List.of(), new TestData("a", 1));
service.store("file2.txt", List.of("level1"), new TestData("b", 2));
service.store("file3.txt", List.of("level1", "level2"), new TestData("c", 3));
service.store("file4.txt", List.of("other"), new TestData("d", 4));
final List<StorageItemDescriptor> items = service.listPath(List.of(), true);
assertThat(items).hasSize(4);
assertThat(items.stream().map(StorageItemDescriptor::name)).containsExactlyInAnyOrder("file1.txt", "file2.txt", "file3.txt", "file4.txt");
}
@Test
void listAllReturnsAllStoredItems() throws Exception {
Files.deleteIfExists(StorageServiceImpl.INDEX_LOCATION);
final StorageService service = new StorageServiceImpl();
service.store("file1.txt", List.of("docs"), new TestData("a", 1));
service.store("file2.txt", List.of("data"), new TestData("b", 2));
service.store("file3.txt", List.of("docs", "sub"), new TestData("c", 3));
final List<StorageItemDescriptor> items = service.listAll();
assertThat(items).hasSize(3);
assertThat(items.stream().map(StorageItemDescriptor::name)).containsExactlyInAnyOrder("file1.txt", "file2.txt", "file3.txt");
}
@Test
void listAllOnEmptyServiceReturnsEmpty() throws Exception {
Files.deleteIfExists(StorageServiceImpl.INDEX_LOCATION);
final StorageService service = new StorageServiceImpl();
final List<StorageItemDescriptor> items = service.listAll();
assertThat(items).isEmpty();
}
@Test
void listAllAfterDeletionReflectsRemovedItems() throws Exception {
Files.deleteIfExists(StorageServiceImpl.INDEX_LOCATION);
final StorageService service = new StorageServiceImpl();
final UUID id = service.store("file.txt", List.of("docs"), new TestData("test", 1));
assertThat(service.listAll()).hasSize(1);
service.deleteById(id);
assertThat(service.listAll()).isEmpty();
}
private StorageIndex getIndex(final StorageService service) throws Exception {
final Field field = StorageServiceImpl.class.getDeclaredField("index");
field.setAccessible(true);
return (StorageIndex) field.get(service);
}
public static record TestData(String value, int number) {
}
}

View File

@@ -4,6 +4,7 @@ import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import java.io.ByteArrayOutputStream;
import java.util.List;
import java.util.Map;
@@ -133,6 +134,112 @@ public class ObjectSerializationTest {
assertEquals(original.value, deserialized.value);
}
@Test
public void testSerializeOutputStreamBasic() {
var original = new TestObject("OutputStreamTest", 42, 3.14, List.of("tag1", "tag2"), List.of(new TestObject2("key", true)), Map.of("attr", 1));
var outputStream = new ByteArrayOutputStream();
ObjectSerialization.serialize(original, outputStream, false, false, false, null, digest -> {});
var serialized = outputStream.toByteArray();
assertNotNull(serialized);
assertTrue(serialized.length > 0);
var deserialized = ObjectSerialization.deserialize(TestObject.class, serialized, false, false, null, null, result -> {});
assertNotNull(deserialized);
assertEquals(original.name, deserialized.name);
assertEquals(original.id, deserialized.id);
assertEquals(original.value, deserialized.value);
}
@Test
public void testSerializeOutputStreamWithCompression() {
var original = new TestObject("CompressedStream", 100, 2.71, List.of("a", "b", "c"), List.of(), Map.of());
var outputStream = new ByteArrayOutputStream();
ObjectSerialization.serialize(original, outputStream, true, false, false, null, digest -> {});
var serialized = outputStream.toByteArray();
assertNotNull(serialized);
assertTrue(serialized.length > 0);
var deserialized = ObjectSerialization.deserialize(TestObject.class, serialized, true, false, null, null, result -> {});
assertNotNull(deserialized);
assertEquals(original.name, deserialized.name);
assertEquals(original.id, deserialized.id);
assertEquals(original.value, deserialized.value);
}
@Test
public void testSerializeOutputStreamWithBase64() {
var original = new TestObject("Base64Stream", 200, 1.41, List.of(), List.of(), Map.of());
var outputStream = new ByteArrayOutputStream();
ObjectSerialization.serialize(original, outputStream, false, true, false, null, digest -> {});
var serialized = outputStream.toByteArray();
assertNotNull(serialized);
assertTrue(serialized.length > 0);
assertTrue(new String(serialized).matches("[A-Za-z0-9+/=]+"));
var deserialized = ObjectSerialization.deserialize(TestObject.class, serialized, false, true, null, null, result -> {});
assertNotNull(deserialized);
assertEquals(original.name, deserialized.name);
assertEquals(original.id, deserialized.id);
assertEquals(original.value, deserialized.value);
}
@Test
public void testSerializeOutputStreamWithDigest() {
var original = new TestObject("DigestedStream", 300, 0.5, List.of("x"), List.of(), Map.of());
var digestSalt = "salt".getBytes();
var capturedDigest = new byte[32][];
var outputStream = new ByteArrayOutputStream();
ObjectSerialization.serialize(original, outputStream, false, false, true, digestSalt, digest -> capturedDigest[0] = digest);
var serialized = outputStream.toByteArray();
assertNotNull(serialized);
assertNotNull(capturedDigest[0]);
assertTrue(capturedDigest[0].length == 32);
var digestCheckPassed = new boolean[1];
var deserialized = ObjectSerialization.deserialize(TestObject.class, serialized, false, false, capturedDigest[0], digestSalt, result -> digestCheckPassed[0] = result);
assertNotNull(deserialized);
assertTrue(digestCheckPassed[0]);
assertEquals(original.name, deserialized.name);
assertEquals(original.id, deserialized.id);
assertEquals(original.value, deserialized.value);
}
@Test
public void testSerializeOutputStreamWithAllOptions() {
var original = new TestObject("AllOptionsStream", 500, 9.99, List.of("all"), List.of(new TestObject2("a", false), new TestObject2("b", true)), Map.of("x", 1, "y", 2));
var digestSalt = "salt123".getBytes();
var capturedDigest = new byte[32][];
var outputStream = new ByteArrayOutputStream();
ObjectSerialization.serialize(original, outputStream, true, true, true, digestSalt, digest -> capturedDigest[0] = digest);
var serialized = outputStream.toByteArray();
assertNotNull(serialized);
assertNotNull(capturedDigest[0]);
assertTrue(new String(serialized).matches("[A-Za-z0-9+/=]+"));
var digestCheckPassed = new boolean[1];
var deserialized = ObjectSerialization.deserialize(TestObject.class, serialized, true, true, capturedDigest[0], digestSalt, result -> digestCheckPassed[0] = result);
assertNotNull(deserialized);
assertTrue(digestCheckPassed[0]);
assertEquals(original.name, deserialized.name);
assertEquals(original.id, deserialized.id);
assertEquals(original.value, deserialized.value);
}
public record TestObject(String name, int id, double value, List<String> tags, List<TestObject2> children, Map<String, Integer> attributes) {
}