M4 AP-004 Dokument-Stammsatz-Repository implementieren
This commit is contained in:
@@ -0,0 +1,285 @@
|
|||||||
|
package de.gecheckt.pdf.umbenenner.adapter.out.sqlite;
|
||||||
|
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.*;
|
||||||
|
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
|
||||||
|
import de.gecheckt.pdf.umbenenner.domain.model.ProcessingStatus;
|
||||||
|
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
import java.sql.*;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SQLite implementation of {@link DocumentRecordRepository}.
|
||||||
|
* <p>
|
||||||
|
* Provides CRUD operations for the document master record (Dokument-Stammsatz)
|
||||||
|
* with explicit mapping between application types and the SQLite schema.
|
||||||
|
* <p>
|
||||||
|
* <strong>Architecture boundary:</strong> All JDBC and SQLite details are strictly
|
||||||
|
* confined to this class. No JDBC types appear in the port interface or in any
|
||||||
|
* application/domain type.
|
||||||
|
*
|
||||||
|
* @since M4-AP-004
|
||||||
|
*/
|
||||||
|
public class SqliteDocumentRecordRepositoryAdapter implements DocumentRecordRepository {
|
||||||
|
|
||||||
|
private static final Logger logger = LogManager.getLogger(SqliteDocumentRecordRepositoryAdapter.class);
|
||||||
|
|
||||||
|
private final String jdbcUrl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs the adapter with the JDBC URL of the SQLite database file.
|
||||||
|
*
|
||||||
|
* @param jdbcUrl the JDBC URL of the SQLite database; must not be null or blank
|
||||||
|
* @throws NullPointerException if {@code jdbcUrl} is null
|
||||||
|
* @throws IllegalArgumentException if {@code jdbcUrl} is blank
|
||||||
|
*/
|
||||||
|
public SqliteDocumentRecordRepositoryAdapter(String jdbcUrl) {
|
||||||
|
Objects.requireNonNull(jdbcUrl, "jdbcUrl must not be null");
|
||||||
|
if (jdbcUrl.isBlank()) {
|
||||||
|
throw new IllegalArgumentException("jdbcUrl must not be blank");
|
||||||
|
}
|
||||||
|
this.jdbcUrl = jdbcUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Looks up the master record for the given fingerprint.
|
||||||
|
* <p>
|
||||||
|
* Returns a {@link DocumentRecordLookupResult} that encodes all possible outcomes
|
||||||
|
* including technical failures; this method never throws.
|
||||||
|
*
|
||||||
|
* @param fingerprint the content-based document identity to look up; must not be null
|
||||||
|
* @return {@link DocumentUnknown} if no record exists,
|
||||||
|
* {@link DocumentKnownProcessable} if the document is known but not terminal,
|
||||||
|
* {@link DocumentTerminalSuccess} if the document succeeded,
|
||||||
|
* {@link DocumentTerminalFinalFailure} if the document finally failed, or
|
||||||
|
* {@link PersistenceLookupTechnicalFailure} if the lookup itself failed
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public DocumentRecordLookupResult findByFingerprint(DocumentFingerprint fingerprint) {
|
||||||
|
if (fingerprint == null) {
|
||||||
|
throw new NullPointerException("fingerprint must not be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
String sql = """
|
||||||
|
SELECT
|
||||||
|
last_known_source_locator,
|
||||||
|
last_known_source_file_name,
|
||||||
|
overall_status,
|
||||||
|
content_error_count,
|
||||||
|
transient_error_count,
|
||||||
|
last_failure_instant,
|
||||||
|
last_success_instant,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
FROM document_record
|
||||||
|
WHERE fingerprint = ?
|
||||||
|
""";
|
||||||
|
|
||||||
|
try (Connection connection = DriverManager.getConnection(jdbcUrl);
|
||||||
|
PreparedStatement statement = connection.prepareStatement(sql)) {
|
||||||
|
|
||||||
|
statement.setString(1, fingerprint.sha256Hex());
|
||||||
|
|
||||||
|
try (ResultSet rs = statement.executeQuery()) {
|
||||||
|
if (rs.next()) {
|
||||||
|
// Document exists - map to appropriate result type based on status
|
||||||
|
DocumentRecord record = mapResultSetToDocumentRecord(rs, fingerprint);
|
||||||
|
|
||||||
|
return switch (record.overallStatus()) {
|
||||||
|
case SUCCESS -> new DocumentTerminalSuccess(record);
|
||||||
|
case FAILED_FINAL -> new DocumentTerminalFinalFailure(record);
|
||||||
|
case PROCESSING, FAILED_RETRYABLE, SKIPPED_ALREADY_PROCESSED, SKIPPED_FINAL_FAILURE ->
|
||||||
|
new DocumentKnownProcessable(record);
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// Document not found
|
||||||
|
return new DocumentUnknown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (SQLException e) {
|
||||||
|
String message = "Failed to lookup document record for fingerprint '" +
|
||||||
|
fingerprint.sha256Hex() + "': " + e.getMessage();
|
||||||
|
logger.error(message, e);
|
||||||
|
return new PersistenceLookupTechnicalFailure(message, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persists a new master record for a previously unknown document.
|
||||||
|
* <p>
|
||||||
|
* The fingerprint within {@code record} must not yet exist in the persistence store.
|
||||||
|
*
|
||||||
|
* @param record the new master record to persist; must not be null
|
||||||
|
* @throws DocumentPersistenceException if the insert fails due to a technical error
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void create(DocumentRecord record) {
|
||||||
|
if (record == null) {
|
||||||
|
throw new NullPointerException("record must not be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
String sql = """
|
||||||
|
INSERT INTO document_record (
|
||||||
|
fingerprint,
|
||||||
|
last_known_source_locator,
|
||||||
|
last_known_source_file_name,
|
||||||
|
overall_status,
|
||||||
|
content_error_count,
|
||||||
|
transient_error_count,
|
||||||
|
last_failure_instant,
|
||||||
|
last_success_instant,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
""";
|
||||||
|
|
||||||
|
try (Connection connection = DriverManager.getConnection(jdbcUrl);
|
||||||
|
PreparedStatement statement = connection.prepareStatement(sql)) {
|
||||||
|
|
||||||
|
statement.setString(1, record.fingerprint().sha256Hex());
|
||||||
|
statement.setString(2, record.lastKnownSourceLocator().value());
|
||||||
|
statement.setString(3, record.lastKnownSourceFileName());
|
||||||
|
statement.setString(4, record.overallStatus().name());
|
||||||
|
statement.setInt(5, record.failureCounters().contentErrorCount());
|
||||||
|
statement.setInt(6, record.failureCounters().transientErrorCount());
|
||||||
|
statement.setString(7, instantToString(record.lastFailureInstant()));
|
||||||
|
statement.setString(8, instantToString(record.lastSuccessInstant()));
|
||||||
|
statement.setString(9, instantToString(record.createdAt()));
|
||||||
|
statement.setString(10, instantToString(record.updatedAt()));
|
||||||
|
|
||||||
|
int rowsAffected = statement.executeUpdate();
|
||||||
|
if (rowsAffected != 1) {
|
||||||
|
throw new DocumentPersistenceException(
|
||||||
|
"Expected to insert 1 row but affected " + rowsAffected + " rows");
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug("Created document record for fingerprint: {}", record.fingerprint().sha256Hex());
|
||||||
|
|
||||||
|
} catch (SQLException e) {
|
||||||
|
String message = "Failed to create document record for fingerprint '" +
|
||||||
|
record.fingerprint().sha256Hex() + "': " + e.getMessage();
|
||||||
|
logger.error(message, e);
|
||||||
|
throw new DocumentPersistenceException(message, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the mutable fields of an existing master record.
|
||||||
|
* <p>
|
||||||
|
* The record is identified by its {@link DocumentFingerprint}; the fingerprint
|
||||||
|
* itself is never changed. Mutable fields include the overall status, failure
|
||||||
|
* counters, last known source location, and all timestamp fields.
|
||||||
|
*
|
||||||
|
* @param record the updated master record; must not be null; fingerprint must exist
|
||||||
|
* @throws DocumentPersistenceException if the update fails due to a technical error
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void update(DocumentRecord record) {
|
||||||
|
if (record == null) {
|
||||||
|
throw new NullPointerException("record must not be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
String sql = """
|
||||||
|
UPDATE document_record SET
|
||||||
|
last_known_source_locator = ?,
|
||||||
|
last_known_source_file_name = ?,
|
||||||
|
overall_status = ?,
|
||||||
|
content_error_count = ?,
|
||||||
|
transient_error_count = ?,
|
||||||
|
last_failure_instant = ?,
|
||||||
|
last_success_instant = ?,
|
||||||
|
updated_at = ?
|
||||||
|
WHERE fingerprint = ?
|
||||||
|
""";
|
||||||
|
|
||||||
|
try (Connection connection = DriverManager.getConnection(jdbcUrl);
|
||||||
|
PreparedStatement statement = connection.prepareStatement(sql)) {
|
||||||
|
|
||||||
|
statement.setString(1, record.lastKnownSourceLocator().value());
|
||||||
|
statement.setString(2, record.lastKnownSourceFileName());
|
||||||
|
statement.setString(3, record.overallStatus().name());
|
||||||
|
statement.setInt(4, record.failureCounters().contentErrorCount());
|
||||||
|
statement.setInt(5, record.failureCounters().transientErrorCount());
|
||||||
|
statement.setString(6, instantToString(record.lastFailureInstant()));
|
||||||
|
statement.setString(7, instantToString(record.lastSuccessInstant()));
|
||||||
|
statement.setString(8, instantToString(record.updatedAt()));
|
||||||
|
statement.setString(9, record.fingerprint().sha256Hex());
|
||||||
|
|
||||||
|
int rowsAffected = statement.executeUpdate();
|
||||||
|
if (rowsAffected != 1) {
|
||||||
|
throw new DocumentPersistenceException(
|
||||||
|
"Expected to update 1 row but affected " + rowsAffected + " rows. " +
|
||||||
|
"Fingerprint may not exist: " + record.fingerprint().sha256Hex());
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug("Updated document record for fingerprint: {}", record.fingerprint().sha256Hex());
|
||||||
|
|
||||||
|
} catch (SQLException e) {
|
||||||
|
String message = "Failed to update document record for fingerprint '" +
|
||||||
|
record.fingerprint().sha256Hex() + "': " + e.getMessage();
|
||||||
|
logger.error(message, e);
|
||||||
|
throw new DocumentPersistenceException(message, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps a ResultSet row to a DocumentRecord.
|
||||||
|
*
|
||||||
|
* @param rs the ResultSet positioned at the current row
|
||||||
|
* @param fingerprint the fingerprint for this record
|
||||||
|
* @return the mapped DocumentRecord
|
||||||
|
* @throws SQLException if reading from the ResultSet fails
|
||||||
|
*/
|
||||||
|
private DocumentRecord mapResultSetToDocumentRecord(ResultSet rs, DocumentFingerprint fingerprint) throws SQLException {
|
||||||
|
return new DocumentRecord(
|
||||||
|
fingerprint,
|
||||||
|
new SourceDocumentLocator(rs.getString("last_known_source_locator")),
|
||||||
|
rs.getString("last_known_source_file_name"),
|
||||||
|
ProcessingStatus.valueOf(rs.getString("overall_status")),
|
||||||
|
new FailureCounters(
|
||||||
|
rs.getInt("content_error_count"),
|
||||||
|
rs.getInt("transient_error_count")
|
||||||
|
),
|
||||||
|
stringToInstant(rs.getString("last_failure_instant")),
|
||||||
|
stringToInstant(rs.getString("last_success_instant")),
|
||||||
|
stringToInstant(rs.getString("created_at")),
|
||||||
|
stringToInstant(rs.getString("updated_at"))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts an Instant to a string representation for storage.
|
||||||
|
*
|
||||||
|
* @param instant the instant to convert, may be null
|
||||||
|
* @return the ISO-8601 string representation, or null if instant is null
|
||||||
|
*/
|
||||||
|
private String instantToString(Instant instant) {
|
||||||
|
return instant != null ? instant.toString() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a string representation back to an Instant.
|
||||||
|
*
|
||||||
|
* @param stringValue the ISO-8601 string representation, may be null
|
||||||
|
* @return the parsed Instant, or null if stringValue is null or blank
|
||||||
|
*/
|
||||||
|
private Instant stringToInstant(String stringValue) {
|
||||||
|
return stringValue != null && !stringValue.isBlank() ? Instant.parse(stringValue) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the JDBC URL this adapter uses to connect to the SQLite database.
|
||||||
|
* <p>
|
||||||
|
* Intended for logging and diagnostics only.
|
||||||
|
*
|
||||||
|
* @return the JDBC URL; never null or blank
|
||||||
|
*/
|
||||||
|
public String getJdbcUrl() {
|
||||||
|
return jdbcUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
package de.gecheckt.pdf.umbenenner.adapter.out.sqlite;
|
||||||
|
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.out.*;
|
||||||
|
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
|
||||||
|
import de.gecheckt.pdf.umbenenner.domain.model.ProcessingStatus;
|
||||||
|
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link SqliteDocumentRecordRepositoryAdapter}.
|
||||||
|
*/
|
||||||
|
class SqliteDocumentRecordRepositoryAdapterTest {
|
||||||
|
|
||||||
|
private SqliteDocumentRecordRepositoryAdapter repository;
|
||||||
|
private String jdbcUrl;
|
||||||
|
|
||||||
|
@TempDir
|
||||||
|
Path tempDir;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
Path dbFile = tempDir.resolve("test.db");
|
||||||
|
jdbcUrl = "jdbc:sqlite:" + dbFile.toAbsolutePath();
|
||||||
|
|
||||||
|
// Initialize schema first
|
||||||
|
SqliteSchemaInitializationAdapter schemaInitializer =
|
||||||
|
new SqliteSchemaInitializationAdapter(jdbcUrl);
|
||||||
|
schemaInitializer.initializeSchema();
|
||||||
|
|
||||||
|
repository = new SqliteDocumentRecordRepositoryAdapter(jdbcUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void findByFingerprint_shouldReturnDocumentUnknown_whenRecordDoesNotExist() {
|
||||||
|
// Given
|
||||||
|
DocumentFingerprint fingerprint = new DocumentFingerprint(
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000");
|
||||||
|
|
||||||
|
// When
|
||||||
|
DocumentRecordLookupResult result = repository.findByFingerprint(fingerprint);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertThat(result).isInstanceOf(DocumentUnknown.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void create_and_findByFingerprint_shouldWorkForNewRecord() {
|
||||||
|
// Given
|
||||||
|
DocumentFingerprint fingerprint = new DocumentFingerprint(
|
||||||
|
"1111111111111111111111111111111111111111111111111111111111111111");
|
||||||
|
DocumentRecord record = new DocumentRecord(
|
||||||
|
fingerprint,
|
||||||
|
new SourceDocumentLocator("/path/to/document.pdf"),
|
||||||
|
"document.pdf",
|
||||||
|
ProcessingStatus.PROCESSING,
|
||||||
|
FailureCounters.zero(),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
Instant.now().truncatedTo(ChronoUnit.MICROS),
|
||||||
|
Instant.now().truncatedTo(ChronoUnit.MICROS)
|
||||||
|
);
|
||||||
|
|
||||||
|
// When
|
||||||
|
repository.create(record);
|
||||||
|
DocumentRecordLookupResult result = repository.findByFingerprint(fingerprint);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertThat(result).isInstanceOf(DocumentKnownProcessable.class);
|
||||||
|
DocumentKnownProcessable known = (DocumentKnownProcessable) result;
|
||||||
|
DocumentRecord foundRecord = known.record();
|
||||||
|
|
||||||
|
assertThat(foundRecord.fingerprint()).isEqualTo(fingerprint);
|
||||||
|
assertThat(foundRecord.lastKnownSourceLocator().value()).isEqualTo("/path/to/document.pdf");
|
||||||
|
assertThat(foundRecord.lastKnownSourceFileName()).isEqualTo("document.pdf");
|
||||||
|
assertThat(foundRecord.overallStatus()).isEqualTo(ProcessingStatus.PROCESSING);
|
||||||
|
assertThat(foundRecord.failureCounters()).isEqualTo(FailureCounters.zero());
|
||||||
|
assertThat(foundRecord.lastFailureInstant()).isNull();
|
||||||
|
assertThat(foundRecord.lastSuccessInstant()).isNull();
|
||||||
|
assertThat(foundRecord.createdAt()).isEqualTo(record.createdAt());
|
||||||
|
assertThat(foundRecord.updatedAt()).isEqualTo(record.updatedAt());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void update_shouldModifyExistingRecord() {
|
||||||
|
// Given
|
||||||
|
DocumentFingerprint fingerprint = new DocumentFingerprint(
|
||||||
|
"2222222222222222222222222222222222222222222222222222222222222222");
|
||||||
|
DocumentRecord initialRecord = new DocumentRecord(
|
||||||
|
fingerprint,
|
||||||
|
new SourceDocumentLocator("/initial/path.pdf"),
|
||||||
|
"initial.pdf",
|
||||||
|
ProcessingStatus.PROCESSING,
|
||||||
|
FailureCounters.zero(),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
Instant.now().minusSeconds(60).truncatedTo(ChronoUnit.MICROS),
|
||||||
|
Instant.now().minusSeconds(60).truncatedTo(ChronoUnit.MICROS)
|
||||||
|
);
|
||||||
|
|
||||||
|
repository.create(initialRecord);
|
||||||
|
|
||||||
|
// Updated record
|
||||||
|
Instant now = Instant.now().truncatedTo(ChronoUnit.MICROS);
|
||||||
|
DocumentRecord updatedRecord = new DocumentRecord(
|
||||||
|
fingerprint,
|
||||||
|
new SourceDocumentLocator("/updated/path.pdf"),
|
||||||
|
"updated.pdf",
|
||||||
|
ProcessingStatus.SUCCESS,
|
||||||
|
new FailureCounters(0, 0),
|
||||||
|
null,
|
||||||
|
now,
|
||||||
|
initialRecord.createdAt(),
|
||||||
|
now
|
||||||
|
);
|
||||||
|
|
||||||
|
// When
|
||||||
|
repository.update(updatedRecord);
|
||||||
|
DocumentRecordLookupResult result = repository.findByFingerprint(fingerprint);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertThat(result).isInstanceOf(DocumentTerminalSuccess.class);
|
||||||
|
DocumentTerminalSuccess success = (DocumentTerminalSuccess) result;
|
||||||
|
DocumentRecord foundRecord = success.record();
|
||||||
|
|
||||||
|
assertThat(foundRecord.lastKnownSourceLocator().value()).isEqualTo("/updated/path.pdf");
|
||||||
|
assertThat(foundRecord.lastKnownSourceFileName()).isEqualTo("updated.pdf");
|
||||||
|
assertThat(foundRecord.overallStatus()).isEqualTo(ProcessingStatus.SUCCESS);
|
||||||
|
assertThat(foundRecord.lastSuccessInstant()).isEqualTo(now);
|
||||||
|
assertThat(foundRecord.updatedAt()).isEqualTo(now);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void create_shouldThrowException_whenFingerprintAlreadyExists() {
|
||||||
|
// Given
|
||||||
|
DocumentFingerprint fingerprint = new DocumentFingerprint(
|
||||||
|
"3333333333333333333333333333333333333333333333333333333333333333");
|
||||||
|
DocumentRecord record1 = new DocumentRecord(
|
||||||
|
fingerprint,
|
||||||
|
new SourceDocumentLocator("/path1.pdf"),
|
||||||
|
"doc1.pdf",
|
||||||
|
ProcessingStatus.PROCESSING,
|
||||||
|
FailureCounters.zero(),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
Instant.now().truncatedTo(ChronoUnit.MICROS),
|
||||||
|
Instant.now().truncatedTo(ChronoUnit.MICROS)
|
||||||
|
);
|
||||||
|
|
||||||
|
repository.create(record1);
|
||||||
|
|
||||||
|
DocumentRecord record2 = new DocumentRecord(
|
||||||
|
fingerprint,
|
||||||
|
new SourceDocumentLocator("/path2.pdf"),
|
||||||
|
"doc2.pdf",
|
||||||
|
ProcessingStatus.PROCESSING,
|
||||||
|
FailureCounters.zero(),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
Instant.now().truncatedTo(ChronoUnit.MICROS),
|
||||||
|
Instant.now().truncatedTo(ChronoUnit.MICROS)
|
||||||
|
);
|
||||||
|
|
||||||
|
// When / Then
|
||||||
|
assertThatThrownBy(() -> repository.create(record2))
|
||||||
|
.isInstanceOf(DocumentPersistenceException.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void update_shouldThrowException_whenFingerprintDoesNotExist() {
|
||||||
|
// Given
|
||||||
|
DocumentFingerprint fingerprint = new DocumentFingerprint(
|
||||||
|
"4444444444444444444444444444444444444444444444444444444444444444");
|
||||||
|
DocumentRecord record = new DocumentRecord(
|
||||||
|
fingerprint,
|
||||||
|
new SourceDocumentLocator("/nonexistent.pdf"),
|
||||||
|
"nonexistent.pdf",
|
||||||
|
ProcessingStatus.PROCESSING,
|
||||||
|
FailureCounters.zero(),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
Instant.now().truncatedTo(ChronoUnit.MICROS),
|
||||||
|
Instant.now().truncatedTo(ChronoUnit.MICROS)
|
||||||
|
);
|
||||||
|
|
||||||
|
// When / Then
|
||||||
|
assertThatThrownBy(() -> repository.update(record))
|
||||||
|
.isInstanceOf(DocumentPersistenceException.class)
|
||||||
|
.hasMessageContaining("Expected to update 1 row but affected 0 rows");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user