Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Sources/TranslationCatalog/CatalogQuery.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation

/// Associated parameters for performing query operations
public protocol CatalogQuery: Sendable {}
public protocol CatalogQuery: Equatable, Sendable {}

public enum GenericProjectQuery: CatalogQuery {
case id(Project.ID)
Expand Down
2 changes: 1 addition & 1 deletion Sources/TranslationCatalog/CatalogUpdate.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation

/// Associated parameters when performing update operations
public protocol CatalogUpdate: Sendable {}
public protocol CatalogUpdate: Equatable, Sendable {}

public enum GenericProjectUpdate: CatalogUpdate {
case name(String)
Expand Down
13 changes: 8 additions & 5 deletions Sources/TranslationCatalogFilesystem/DirectoryCatalog.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@ public typealias FilesystemCatalog = DirectoryCatalog
/// Implementation of `Catalog` the reads/writes data from/to a filesystem directory.
public class DirectoryCatalog: FilesystemContainer {

let medium: URL
let url: URL
var translationDocuments: [TranslationDocument] = []
var expressionDocuments: [ExpressionDocument] = []
var projectDocuments: [ProjectDocument] = []

@available(*, deprecated, renamed: "url")
var medium: URL { url }

var translationContainer: URL {
guard let url = try? directory(forPath: Self.translationsPath) else {
preconditionFailure("Invalid Translation Directory")
Expand Down Expand Up @@ -43,7 +46,7 @@ public class DirectoryCatalog: FilesystemContainer {
throw URLError(.unsupportedURL)
}

medium = url
self.url = url
if let schemaVersion = getSchemaVersion() {
try migrateSchema(from: schemaVersion, to: .current)
try loadAllDocuments()
Expand All @@ -54,7 +57,7 @@ public class DirectoryCatalog: FilesystemContainer {
}

private func directory(forPath path: String) throws -> URL {
let url = medium.appending(path: path, directoryHint: .isDirectory)
let url = url.appending(path: path, directoryHint: .isDirectory)
if !fileManager.fileExists(atPath: url.path()) {
try fileManager.createDirectory(at: url, withIntermediateDirectories: true)
}
Expand Down Expand Up @@ -102,7 +105,7 @@ public class DirectoryCatalog: FilesystemContainer {
}

func getSchemaVersion(using decoder: JSONDecoder) -> DocumentSchemaVersion? {
let url = medium.appending(path: Self.versionPath, directoryHint: .notDirectory)
let url = url.appending(path: Self.versionPath, directoryHint: .notDirectory)

do {
let data = try Data(contentsOf: url)
Expand All @@ -114,7 +117,7 @@ public class DirectoryCatalog: FilesystemContainer {
}

func setSchemaVersion(_ version: DocumentSchemaVersion, using encoder: JSONEncoder) throws {
let url = medium.appending(path: Self.versionPath, directoryHint: .notDirectory)
let url = url.appending(path: Self.versionPath, directoryHint: .notDirectory)
let data = try encoder.encode(version.rawValue)
try data.write(to: url)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ public extension SQLiteCatalog {

enum Error: Swift.Error {
case invalidPrimaryKey(Int)
@available(*, deprecated, message: "Use CatalogError.badQuery()")
case invalidStringValue(String)
}

Expand Down
4 changes: 3 additions & 1 deletion Sources/TranslationCatalogSQLite/SQLiteCatalog.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import TranslationCatalog
public class SQLiteCatalog: TranslationCatalog.Catalog {
public typealias RenderedStatementHook = (String) -> Void

let url: URL
private let db: Connection
/// A hook to observe statements that are rendered and executed.
public var statementHook: RenderedStatementHook?

public init(url: URL) throws {
self.url = url
db = try Connection(url: url)
}

Expand Down Expand Up @@ -80,7 +82,7 @@ public class SQLiteCatalog: TranslationCatalog.Catalog {
return try entity.project()
case GenericProjectQuery.named(let name):
guard let entity = try db.projectEntity(statement: renderStatement(.selectProject(withName: name))) else {
throw Error.invalidStringValue(name)
throw CatalogError.badQuery(query)
}

return try entity.project()
Expand Down
181 changes: 181 additions & 0 deletions Tests/TranslationCatalogTests/CatalogDeleteTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import Foundation
import Testing
@testable @preconcurrency import TranslationCatalog
#if canImport(CoreData)
@testable import TranslationCatalogCoreData
#endif
@testable import TranslationCatalogFilesystem
@testable import TranslationCatalogSQLite

final class CatalogDeleteTests {

private static var container: TestContainer {
get throws {
try TestContainer()
}
}

deinit {
do {
try Self.container.recycle()
} catch {}
}

/// Verify that a `Project` can be removed from the catalog.
@Test(arguments: try Self.container.catalogs)
func deleteProject(catalog: any Catalog) throws {
let projectId = try #require(UUID(uuidString: "06937A10-2E46-4FFD-A2E7-60A3F03ED007"))
let project = Project(
id: projectId,
name: "Example Project"
)

try catalog.createProject(project)

var projects = try catalog.projects()
#expect(projects.count == 1)

try catalog.deleteProject(projectId)

projects = try catalog.projects()
#expect(projects.count == 0)
}

/// Verify that a `Expression` can be removed from the catalog.
@Test(arguments: try Self.container.catalogs)
func deleteExpression(catalog: any Catalog) throws {
let expressionId = try #require(UUID(uuidString: "0503A67E-EAC5-4612-A91A-559477283C56"))
let expression = Expression(
id: expressionId,
key: "TEST_EXPRESSION",
value: "Test Expression",
languageCode: .english
)

try catalog.createExpression(expression)

var expressions = try catalog.expressions()
#expect(expressions.count == 1)

try catalog.deleteExpression(expressionId)

expressions = try catalog.expressions()
#expect(expressions.count == 0)
}

/// Verify that a `Translation` can be removed from the catalog.
@Test(arguments: try Self.container.catalogs)
func deleteTranslation(catalog: any Catalog) throws {
let expressionId = try #require(UUID(uuidString: "F590AA58-626D-4EAB-AEDA-21F047B9BA42"))
let expression = Expression(
id: expressionId,
key: "TRACK_TITLE",
value: "Track Title",
languageCode: .english
)

let translationId = try #require(UUID(uuidString: "A93E74CD-58F2-4D00-BA6B-F722FFCCCFBF"))
let translation = TranslationCatalog.Translation(
id: translationId,
expressionId: expressionId,
value: "Overture to Egmont, Op. 84",
language: .english,
region: .unitedStates,
state: .translated
)

try catalog.createExpression(expression)
try catalog.createTranslation(translation)

var expressions = try catalog.expressions()
#expect(expressions.count == 1)

var translations = try catalog.translations()
#expect(translations.count == 1)

try catalog.deleteTranslation(translationId)

expressions = try catalog.expressions()
#expect(expressions.count == 1)

translations = try catalog.translations()
#expect(translations.count == 0)
}

/// Verify that a `Expression` can be removed from the catalog, and it's related
/// `Translation` entities are also removed.
@Test(arguments: try Self.container.catalogs)
func deleteExpression_CascadeTranslation(catalog: any Catalog) throws {
let expressionId = try #require(UUID(uuidString: "F590AA58-626D-4EAB-AEDA-21F047B9BA42"))
let expression = Expression(
id: expressionId,
key: "TRACK_TITLE",
value: "Track Title",
languageCode: .english
)

let translationId = try #require(UUID(uuidString: "A93E74CD-58F2-4D00-BA6B-F722FFCCCFBF"))
let translation = TranslationCatalog.Translation(
id: translationId,
expressionId: expressionId,
value: "Overture to Egmont, Op. 84",
language: .english,
region: .unitedStates,
state: .translated
)

try catalog.createExpression(expression)
try catalog.createTranslation(translation)

var expressions = try catalog.expressions()
#expect(expressions.count == 1)

var translations = try catalog.translations()
#expect(translations.count == 1)

try catalog.deleteExpression(expressionId)

expressions = try catalog.expressions()
#expect(expressions.count == 0)

translations = try catalog.translations()
#expect(translations.count == 0)
}

/// Verify that a `Project` can be removed from the catalog, and it's related
/// `Expression` entities remain intact.
@Test(arguments: try Self.container.catalogs)
func deleteProject_NullifyExpression(catalog: any Catalog) throws {
let projectId = try #require(UUID(uuidString: "06937A10-2E46-4FFD-A2E7-60A3F03ED007"))
let project = Project(
id: projectId,
name: "Example Project"
)

let expressionId = try #require(UUID(uuidString: "F590AA58-626D-4EAB-AEDA-21F047B9BA42"))
let expression = Expression(
id: expressionId,
key: "TRACK_TITLE",
value: "Track Title",
languageCode: .english
)

try catalog.createProject(project)
try catalog.createExpression(expression)
try catalog.updateProject(projectId, action: GenericProjectUpdate.linkExpression(expressionId))

var projects = try catalog.projects()
#expect(projects.count == 1)

var expressions = try catalog.expressions()
#expect(expressions.count == 1)

try catalog.deleteProject(projectId)

projects = try catalog.projects()
#expect(projects.count == 0)

expressions = try catalog.expressions()
#expect(expressions.count == 1)
}
}
Loading
Loading