new tests
This commit is contained in:
@@ -6,6 +6,14 @@
|
||||
objectVersion = 56;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
F0737000000000000000000D /* DBSyn target proxy */ = { isa = PBXContainerItemProxy; containerPortal = 000000000000000000000001; proxyType = 1; remoteGlobalIDString = 000000000000000000000005; remoteInfo = DBSyn; };
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
F0737000000000000000000E /* DBSyn dependency */ = { isa = PBXTargetDependency; target = 000000000000000000000005; targetProxy = F0737000000000000000000D; };
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
B73200000000000000000001 /* SchemaDiagramPrintView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B73200000000000000000002 /* SchemaDiagramPrintView.swift */; };
|
||||
B73000000000000000000001 /* SchemaDiagramView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B73000000000000000000002 /* SchemaDiagramView.swift */; };
|
||||
@@ -16,6 +24,8 @@
|
||||
|
||||
A10000000000000000000003 /* DBSyn.icns in Resources */ = {isa = PBXBuildFile; fileRef = A10000000000000000000001 /* DBSyn.icns */; };
|
||||
A10000000000000000000004 /* DBSynBrand.png in Resources */ = {isa = PBXBuildFile; fileRef = A10000000000000000000002 /* DBSynBrand.png */; };
|
||||
F07370000000000000000002 /* DiagramLogic.swift in Sources */ = {isa = PBXBuildFile; fileRef = F07370000000000000000001 /* DiagramLogic.swift */; };
|
||||
F07370000000000000000006 /* DBSynTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F07370000000000000000005 /* DBSynTests.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
@@ -29,6 +39,9 @@
|
||||
|
||||
A10000000000000000000001 /* DBSyn.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = DBSyn.icns; sourceTree = "<group>"; };
|
||||
A10000000000000000000002 /* DBSynBrand.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = DBSynBrand.png; sourceTree = "<group>"; };
|
||||
F07370000000000000000001 /* DiagramLogic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiagramLogic.swift; sourceTree = "<group>"; };
|
||||
F07370000000000000000005 /* DBSynTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBSynTests.swift; sourceTree = "<group>"; };
|
||||
F07370000000000000000007 /* DBSynTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DBSynTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -39,6 +52,7 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
F07370000000000000000009 /* Tests Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = (); runOnlyForDeploymentPostprocessing = 0; };
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
@@ -46,6 +60,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
000000000000000000000003 /* DBSyn */,
|
||||
F07370000000000000000003 /* DBSynTests */,
|
||||
000000000000000000000004 /* Products */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
@@ -56,6 +71,7 @@
|
||||
000000000000000000000010 /* App.swift */,
|
||||
000000000000000000000011 /* MainView.swift */,
|
||||
000000000000000000000012 /* DatabaseModel.swift */,
|
||||
F07370000000000000000001 /* DiagramLogic.swift */,
|
||||
B73000000000000000000002 /* SchemaDiagramView.swift */,
|
||||
B73200000000000000000002 /* SchemaDiagramPrintView.swift */,
|
||||
000000000000000000000016 /* MySQLProtocol.swift */,
|
||||
@@ -68,6 +84,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
00000000000000000000000F /* DBSyn.app */,
|
||||
F07370000000000000000007 /* DBSynTests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@@ -82,6 +99,7 @@
|
||||
path = Resources;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
F07370000000000000000003 /* DBSynTests */ = { isa = PBXGroup; children = (F07370000000000000000005 /* DBSynTests.swift */,); path = DBSynTests; sourceTree = "<group>"; };
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
@@ -102,6 +120,7 @@
|
||||
productReference = 00000000000000000000000F /* DBSyn.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
F07370000000000000000004 /* DBSynTests */ = { isa = PBXNativeTarget; buildConfigurationList = F0737000000000000000000A /* Test Configurations */; buildPhases = (F07370000000000000000008 /* Test Sources */, F07370000000000000000009 /* Tests Frameworks */,); buildRules = (); dependencies = (F0737000000000000000000E /* DBSyn dependency */,); name = DBSynTests; productName = DBSynTests; productReference = F07370000000000000000007 /* DBSynTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; };
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
@@ -113,6 +132,7 @@
|
||||
000000000000000000000005 = {
|
||||
CreatedOnToolsVersion = 16.0;
|
||||
};
|
||||
F07370000000000000000004 = { CreatedOnToolsVersion = 16.0; TestTargetID = 000000000000000000000005; };
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 000000000000000000000009 /* Build configuration list for PBXProject "DBSyn" */;
|
||||
@@ -129,6 +149,7 @@
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
000000000000000000000005 /* DBSyn */,
|
||||
F07370000000000000000004 /* DBSynTests */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
@@ -153,12 +174,14 @@
|
||||
000000000000000000000013 /* App.swift in Sources */,
|
||||
000000000000000000000014 /* MainView.swift in Sources */,
|
||||
000000000000000000000015 /* DatabaseModel.swift in Sources */,
|
||||
F07370000000000000000002 /* DiagramLogic.swift in Sources */,
|
||||
B73000000000000000000001 /* SchemaDiagramView.swift in Sources */,
|
||||
B73200000000000000000001 /* SchemaDiagramPrintView.swift in Sources */,
|
||||
000000000000000000000017 /* MySQLProtocol.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
F07370000000000000000008 /* Test Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = (F07370000000000000000006 /* DBSynTests.swift in Sources */,); runOnlyForDeploymentPostprocessing = 0; };
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
@@ -226,6 +249,8 @@
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
F0737000000000000000000B /* Tests Debug */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGNING_ALLOWED = NO; GENERATE_INFOPLIST_FILE = YES; MACOSX_DEPLOYMENT_TARGET = 15.0; PRODUCT_BUNDLE_IDENTIFIER = net.absdev.DBSynTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/DBSyn.app/Contents/MacOS/DBSyn"; }; name = Debug; };
|
||||
F0737000000000000000000C /* Tests Release */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGNING_ALLOWED = NO; GENERATE_INFOPLIST_FILE = YES; MACOSX_DEPLOYMENT_TARGET = 15.0; PRODUCT_BUNDLE_IDENTIFIER = net.absdev.DBSynTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/DBSyn.app/Contents/MacOS/DBSyn"; }; name = Release; };
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
@@ -247,6 +272,7 @@
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
F0737000000000000000000A /* Test Configurations */ = { isa = XCConfigurationList; buildConfigurations = (F0737000000000000000000B /* Tests Debug */, F0737000000000000000000C /* Tests Release */,); defaultConfigurationIsVisible = 0; defaultConfigurationName = Debug; };
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 000000000000000000000001 /* Project object */;
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme LastUpgradeVersion="1600" version="1.7">
|
||||
<BuildAction parallelizeBuildables="YES" buildImplicitDependencies="YES"><BuildActionEntries><BuildActionEntry buildForTesting="YES" buildForRunning="YES" buildForProfiling="YES" buildForArchiving="YES" buildForAnalyzing="YES"><BuildableReference BuildableIdentifier="primary" BlueprintIdentifier="000000000000000000000005" BuildableName="DBSyn.app" BlueprintName="DBSyn" ReferencedContainer="container:DBSyn.xcodeproj"/></BuildActionEntry></BuildActionEntries></BuildAction>
|
||||
<TestAction buildConfiguration="Debug" selectedDebuggerIdentifier="Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier="Xcode.IDEFoundation.Launcher.Posix" shouldUseLaunchSchemeArgsEnv="YES"><Testables><TestableReference skipped="NO"><BuildableReference BuildableIdentifier="primary" BlueprintIdentifier="F07370000000000000000004" BuildableName="DBSynTests.xctest" BlueprintName="DBSynTests" ReferencedContainer="container:DBSyn.xcodeproj"/></TestableReference></Testables></TestAction>
|
||||
<LaunchAction buildConfiguration="Debug" selectedDebuggerIdentifier="Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier="Xcode.IDEFoundation.Launcher.Posix" launchStyle="0" useCustomWorkingDirectory="NO" ignoresPersistentStateOnLaunch="NO" debugDocumentVersioning="YES" debugServiceExtension="internal" allowLocationSimulation="YES"><BuildableProductRunnable runnableDebuggingMode="0"><BuildableReference BuildableIdentifier="primary" BlueprintIdentifier="000000000000000000000005" BuildableName="DBSyn.app" BlueprintName="DBSyn" ReferencedContainer="container:DBSyn.xcodeproj"/></BuildableProductRunnable></LaunchAction>
|
||||
<ProfileAction buildConfiguration="Release" shouldUseLaunchSchemeArgsEnv="YES" savedToolIdentifier="" useCustomWorkingDirectory="NO" debugDocumentVersioning="YES"><BuildableProductRunnable runnableDebuggingMode="0"><BuildableReference BuildableIdentifier="primary" BlueprintIdentifier="000000000000000000000005" BuildableName="DBSyn.app" BlueprintName="DBSyn" ReferencedContainer="container:DBSyn.xcodeproj"/></BuildableProductRunnable></ProfileAction>
|
||||
<AnalyzeAction buildConfiguration="Debug"/><ArchiveAction buildConfiguration="Release" revealArchiveInOrganizer="YES"/>
|
||||
</Scheme>
|
||||
+251
-1050
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,25 @@
|
||||
import Foundation
|
||||
import CoreGraphics
|
||||
|
||||
/// Pure schema-diagram operations shared by the UI and XCTest suite.
|
||||
enum DiagramLogic {
|
||||
static func selectedObjects(_ requested: Set<String>, available: [String]) -> Set<String> {
|
||||
requested.intersection(Set(available))
|
||||
}
|
||||
|
||||
static func arrangedPositions(_ names: [String]) -> [String: CGPoint] {
|
||||
let countPerRow = max(1, Int(ceil(sqrt(Double(max(1, names.count))))))
|
||||
var positions: [String: CGPoint] = [:]
|
||||
for (index, name) in names.enumerated() {
|
||||
positions[name] = CGPoint(x: CGFloat(index % countPerRow) * 290 + 30,
|
||||
y: CGFloat(index / countPerRow) * 300 + 30)
|
||||
}
|
||||
return positions
|
||||
}
|
||||
|
||||
static func translatedPosition(_ origin: CGPoint, translation: CGSize, zoom: CGFloat) -> CGPoint {
|
||||
let scale = max(zoom, 0.1)
|
||||
return CGPoint(x: origin.x + translation.width / scale,
|
||||
y: origin.y + translation.height / scale)
|
||||
}
|
||||
}
|
||||
@@ -355,7 +355,7 @@ struct DBSynSchemaDiagramView: View {
|
||||
// previous implementation only saved the checkboxes, leaving the diagram
|
||||
// scrolled to stale positions after None / All / individual selections.
|
||||
private func updateIncludedObjects(_ updated: Set<String>) {
|
||||
let valid = updated.intersection(Set(allObjects))
|
||||
let valid = DiagramLogic.selectedObjects(updated, available: allObjects)
|
||||
guard valid != includedObjects else { return }
|
||||
includedObjects = valid
|
||||
if let selectedObject, !valid.contains(selectedObject) {
|
||||
@@ -371,11 +371,7 @@ struct DBSynSchemaDiagramView: View {
|
||||
|
||||
private func arrange() {
|
||||
let names = allObjects.filter { includedObjects.contains($0) }
|
||||
let countPerRow = max(1, Int(ceil(sqrt(Double(max(1, names.count))))))
|
||||
for (index, name) in names.enumerated() {
|
||||
positions[name] = CGPoint(x: CGFloat(index % countPerRow) * 290 + 30,
|
||||
y: CGFloat(index / countPerRow) * 300 + 30)
|
||||
}
|
||||
positions.merge(DiagramLogic.arrangedPositions(names)) { _, latest in latest }
|
||||
}
|
||||
private func loadCatalog() {
|
||||
let stored = UserDefaults.standard.stringArray(forKey: catalogKey) ?? ["Main"]
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
import XCTest
|
||||
@testable import DBSyn
|
||||
|
||||
final class DBSynTests: XCTestCase {
|
||||
func testSQLSplitsMultipleStatements() {
|
||||
XCTAssertEqual(SQLStatementSplitter.split("USE AAMEDIUK; SELECT * FROM ADCUSERS;"),
|
||||
["USE AAMEDIUK", "SELECT * FROM ADCUSERS"])
|
||||
}
|
||||
|
||||
func testSQLSplitterPreservesQuotedSemicolons() {
|
||||
XCTAssertEqual(SQLStatementSplitter.split("SELECT 'a;b'; SELECT `a;b` FROM t;"),
|
||||
["SELECT 'a;b'", "SELECT `a;b` FROM t"])
|
||||
}
|
||||
|
||||
func testSQLSplitterIgnoresComments() {
|
||||
XCTAssertEqual(SQLStatementSplitter.split("# comment;\nUSE db; /* comment; */ SELECT 1;"),
|
||||
["USE db", "SELECT 1"])
|
||||
}
|
||||
|
||||
func testSQLSplitterIgnoresEmptyStatements() {
|
||||
XCTAssertEqual(SQLStatementSplitter.split(";; SELECT 1;;;"), ["SELECT 1"])
|
||||
}
|
||||
|
||||
func testUSEDatabaseUnquoted() {
|
||||
XCTAssertEqual(SQLStatementSplitter.usedDatabase("USE AAMEDIUK"), "AAMEDIUK")
|
||||
}
|
||||
|
||||
func testUSEDatabaseQuoted() {
|
||||
XCTAssertEqual(SQLStatementSplitter.usedDatabase("USE `my``db`"), "my`db")
|
||||
}
|
||||
|
||||
func testUSEDatabaseRejectsOtherStatements() {
|
||||
XCTAssertNil(SQLStatementSplitter.usedDatabase("SELECT * FROM ADCUSERS"))
|
||||
}
|
||||
|
||||
func testSQLFormatterUppercasesKeywords() {
|
||||
XCTAssertEqual(SQLFormatter.uppercaseKeywords("select * from ADCUSERS where id = 1"),
|
||||
"SELECT * FROM ADCUSERS WHERE id = 1")
|
||||
}
|
||||
|
||||
func testSQLFormatterPreservesStringLiteralsAndComments() {
|
||||
let input = "select 'select from' # select stays lower\nfrom users"
|
||||
XCTAssertEqual(SQLFormatter.uppercaseKeywords(input),
|
||||
"SELECT 'select from' # select stays lower\nFROM users")
|
||||
}
|
||||
|
||||
func testConnectionProfileCodableRoundTrip() throws {
|
||||
var profile = ConnectionProfile()
|
||||
profile.name = "Development"
|
||||
profile.database = "AAMEDIUK"
|
||||
let decoded = try JSONDecoder().decode(ConnectionProfile.self, from: JSONEncoder().encode(profile))
|
||||
XCTAssertEqual(decoded, profile)
|
||||
}
|
||||
|
||||
func testIntrospectionCacheCodableRoundTrip() throws {
|
||||
let connectionID = UUID()
|
||||
let column = DatabaseModel.IntrospectedColumn(table: "ADCUSERS", name: "id", dataType: "bigint", nullable: false, key: "PRI", defaultValue: nil, extra: "auto_increment")
|
||||
let snapshot = DatabaseModel.SchemaIntrospection(connectionID: connectionID, database: "AAMEDIUK", loadedAt: Date(timeIntervalSince1970: 1000), tables: ["ADCUSERS"], views: ["ACTIVE_USERS"], columnsByTable: ["ADCUSERS": [column]])
|
||||
let decoded = try JSONDecoder().decode(DatabaseModel.SchemaIntrospection.self, from: JSONEncoder().encode(snapshot))
|
||||
XCTAssertEqual(decoded.connectionID, connectionID)
|
||||
XCTAssertEqual(decoded.database, "AAMEDIUK")
|
||||
XCTAssertEqual(decoded.tables, ["ADCUSERS"])
|
||||
XCTAssertEqual(decoded.views, ["ACTIVE_USERS"])
|
||||
XCTAssertEqual(decoded.columnsByTable["ADCUSERS"]?.first?.name, "id")
|
||||
}
|
||||
|
||||
func testDiagramSelectNone() {
|
||||
XCTAssertEqual(DiagramLogic.selectedObjects([], available: ["A", "B"]), [])
|
||||
}
|
||||
|
||||
func testDiagramSelectSubsetAndIgnoreRemovedObjects() {
|
||||
XCTAssertEqual(DiagramLogic.selectedObjects(["A", "MISSING"], available: ["A", "B"]), ["A"])
|
||||
}
|
||||
|
||||
func testDiagramSelectAll() {
|
||||
let available = ["A", "B", "VIEW_C"]
|
||||
XCTAssertEqual(DiagramLogic.selectedObjects(Set(available), available: available), Set(available))
|
||||
}
|
||||
|
||||
func testDiagramLayoutContainsEverySelectedTable() {
|
||||
let names = (0..<25).map { "table_\($0)" }
|
||||
let positions = DiagramLogic.arrangedPositions(names)
|
||||
XCTAssertEqual(positions.count, names.count)
|
||||
XCTAssertEqual(positions["table_0"], CGPoint(x: 30, y: 30))
|
||||
XCTAssertEqual(Set(positions.values.map { "\($0.x),\($0.y)" }).count, names.count)
|
||||
}
|
||||
|
||||
func testDiagramDragRespectsZoom() {
|
||||
let origin = CGPoint(x: 100, y: 200)
|
||||
XCTAssertEqual(DiagramLogic.translatedPosition(origin, translation: CGSize(width: 60, height: -40), zoom: 2),
|
||||
CGPoint(x: 130, y: 180))
|
||||
}
|
||||
|
||||
func testDiagramDragHandlesZeroZoom() {
|
||||
let point = DiagramLogic.translatedPosition(.zero, translation: CGSize(width: 10, height: 5), zoom: 0)
|
||||
XCTAssertTrue(point.x.isFinite && point.y.isFinite)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
# DBSyn automated tests
|
||||
|
||||
Run from the project root: `./run-tests.sh`.
|
||||
|
||||
The script runs `xcodebuild test` on the macOS arm64 destination, saves a full log and `.xcresult` bundle under `test-results/`, and returns a nonzero exit status if tests fail.
|
||||
|
||||
The XCTest target covers SQL statement splitting, quoting, comment handling, `USE` parsing, keyword formatting, profile and introspection persistence encoding, diagram selection and layout, and drag/zoom arithmetic.
|
||||
|
||||
**Coverage limits:** These are deterministic unit tests, not a guarantee of complete application coverage. Database connection, authentication, real MySQL execution, AppKit editor interaction, PDF/PNG/SVG export, and printing require separate integration/UI tests on macOS and, for MySQL tests, a dedicated disposable database. No tests should run destructive SQL against production databases.
|
||||
|
||||
This project has not been built or run with Xcode in the packaging environment.
|
||||
Executable
+26
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cd "$ROOT"
|
||||
if ! command -v xcodebuild >/dev/null 2>&1; then
|
||||
echo "ERROR: Xcode and xcodebuild are required on macOS." >&2
|
||||
exit 1
|
||||
fi
|
||||
mkdir -p test-results
|
||||
STAMP="$(date +%Y%m%d-%H%M%S)"
|
||||
RESULT="$ROOT/test-results/DBSyn-$STAMP.xcresult"
|
||||
LOG="$ROOT/test-results/DBSyn-$STAMP.log"
|
||||
echo "Running DBSyn XCTest suite. Log: $LOG"
|
||||
set +e
|
||||
xcodebuild test \
|
||||
-project DBSyn.xcodeproj \
|
||||
-scheme DBSyn \
|
||||
-configuration Debug \
|
||||
-destination 'platform=macOS,arch=arm64' \
|
||||
-derivedDataPath "$ROOT/build" \
|
||||
-resultBundlePath "$RESULT" \
|
||||
CODE_SIGNING_ALLOWED=NO 2>&1 | tee "$LOG"
|
||||
STATUS=${PIPESTATUS[0]}
|
||||
set -e
|
||||
printf '\nTest log: %s\nTest results: %s\n' "$LOG" "$RESULT"
|
||||
exit "$STATUS"
|
||||
Reference in New Issue
Block a user