import XCTest import CryptoKit @testable import CxIDE // MARK: - Credential Store Tests final class CredentialStoreTests: XCTestCase { private var store: CredentialStore! private var tempDir: URL! override func setUpWithError() throws { store = CredentialStore() tempDir = FileManager.default.temporaryDirectory .appendingPathComponent("cxide-test-\(UUID().uuidString)") try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true) } override func tearDownWithError() throws { try? FileManager.default.removeItem(at: tempDir) } func testSetAndGet() { store.set("TEST_KEY", value: "hello") XCTAssertEqual(store.get("TEST_KEY"), "hello") } func testGetMissing() { XCTAssertNil(store.get("NONEXISTENT")) } func testRemove() { store.set("KEY", value: "val") XCTAssertTrue(store.has("KEY")) store.remove("KEY") XCTAssertFalse(store.has("KEY")) XCTAssertNil(store.get("KEY")) } func testKeys() { store.set("B_KEY", value: "1") store.set("A_KEY", value: "2") let keys = store.keys() XCTAssertEqual(keys, ["A_KEY", "B_KEY"]) // sorted } func testHas() { XCTAssertFalse(store.has("FOO")) store.set("FOO", value: "bar") XCTAssertTrue(store.has("FOO")) } func testSaveAndLoad() throws { store.set("AWS_KEY", value: "AKIAEXAMPLE") store.set("AWS_SECRET", value: "secretValue123") try store.save(to: tempDir) let store2 = CredentialStore() try store2.load(from: tempDir) XCTAssertEqual(store2.get("AWS_KEY"), "AKIAEXAMPLE") XCTAssertEqual(store2.get("AWS_SECRET"), "secretValue123") } func testLoadFromEmptyDir() throws { let emptyDir = tempDir.appendingPathComponent("empty") try FileManager.default.createDirectory(at: emptyDir, withIntermediateDirectories: true) try store.load(from: emptyDir) // Should not throw XCTAssertTrue(store.keys().isEmpty) } func testOverwriteValue() { store.set("KEY", value: "first") store.set("KEY", value: "second") XCTAssertEqual(store.get("KEY"), "second") } func testDeleteStore() throws { store.set("DATA", value: "val") try store.save(to: tempDir) try store.deleteStore(in: tempDir) XCTAssertTrue(store.keys().isEmpty) } func testGoDaddyConvenience() { store.setGoDaddyOTE(key: "oteKey", secret: "oteSecret") XCTAssertEqual(store.godaddyOTEKey, "oteKey") XCTAssertEqual(store.godaddyOTESecret, "oteSecret") store.setGoDaddyProduction(key: "prodKey", secret: "prodSecret") XCTAssertEqual(store.godaddyProdKey, "prodKey") XCTAssertEqual(store.godaddyProdSecret, "prodSecret") } func testAWSConvenience() { store.setAWS(accessKeyId: "AKIA123", secretAccessKey: "secret456", region: "eu-west-1") XCTAssertEqual(store.awsAccessKeyId, "AKIA123") XCTAssertEqual(store.awsSecretAccessKey, "secret456") XCTAssertEqual(store.awsDefaultRegion, "eu-west-1") } func testAWSDefaultRegion() { XCTAssertEqual(store.awsDefaultRegion, "us-east-1") // default when not set } func testCredentialErrorDescriptions() { let e1 = CredentialStore.CredentialError.encryptionFailed XCTAssertNotNil(e1.errorDescription) XCTAssertTrue(e1.errorDescription!.contains("encrypt")) let e2 = CredentialStore.CredentialError.decryptionFailed XCTAssertTrue(e2.errorDescription!.contains("decrypt")) let e3 = CredentialStore.CredentialError.invalidFormat XCTAssertTrue(e3.errorDescription!.contains("Invalid")) } func testEncryptedFilePermissions() throws { store.set("KEY", value: "value") try store.save(to: tempDir) let fileURL = tempDir.appendingPathComponent(".cxide-credentials.enc") let attrs = try FileManager.default.attributesOfItem(atPath: fileURL.path) let perms = attrs[.posixPermissions] as? Int XCTAssertEqual(perms, 0o600) } } // MARK: - AWS XML Parser Tests final class AWSXMLParserTests: XCTestCase { func testParseSimpleElement() throws { let xml = "hello" let root = try AWSXMLParser().parse(data: Data(xml.utf8)) XCTAssertEqual(root.name, "Root") XCTAssertEqual(root.child("Name")?.text, "hello") } func testParseNestedElements() throws { let xml = """ bucket-a bucket-b """ let root = try AWSXMLParser().parse(data: Data(xml.utf8)) let buckets = root.allDescendants("Bucket") XCTAssertEqual(buckets.count, 2) XCTAssertEqual(buckets[0].child("Name")?.text, "bucket-a") XCTAssertEqual(buckets[1].child("Name")?.text, "bucket-b") } func testDescendantSearch() throws { let xml = """ deep """ let root = try AWSXMLParser().parse(data: Data(xml.utf8)) XCTAssertEqual(root.descendant("D")?.text, "deep") XCTAssertNil(root.descendant("Missing")) } func testAllDescendants() throws { let xml = """ 1 2 3 """ let root = try AWSXMLParser().parse(data: Data(xml.utf8)) let values = root.allDescendants("Value") XCTAssertEqual(values.count, 3) XCTAssertEqual(values.map(\.text), ["1", "2", "3"]) } func testChildVsDescendant() throws { let xml = "directnested" let root = try AWSXMLParser().parse(data: Data(xml.utf8)) // child only finds direct children XCTAssertEqual(root.child("B")?.text, "direct") // allChildren only finds direct children XCTAssertEqual(root.allChildren("B").count, 1) // allDescendants finds all XCTAssertEqual(root.allDescendants("B").count, 2) } func testEmptyElement() throws { let xml = "" let root = try AWSXMLParser().parse(data: Data(xml.utf8)) XCTAssertEqual(root.child("Empty")?.text, "") } func testInvalidXMLThrows() { let bad = "" XCTAssertThrowsError(try AWSXMLParser().parse(data: Data(bad.utf8))) } func testParseS3ListBucketsResponse() throws { let xml = """ owner123 my-website 2026-01-15T10:30:00.000Z my-backups 2026-03-20T14:00:00.000Z """ let root = try AWSXMLParser().parse(data: Data(xml.utf8)) let buckets = root.allDescendants("Bucket") XCTAssertEqual(buckets.count, 2) XCTAssertEqual(buckets[0].child("Name")?.text, "my-website") XCTAssertEqual(buckets[1].child("Name")?.text, "my-backups") } func testParseS3ListObjectsResponse() throws { let xml = """ my-site index.html 2048 2026-04-01T12:00:00.000Z "abc123" css/style.css 512 2026-04-01T12:00:01.000Z "def456" """ let root = try AWSXMLParser().parse(data: Data(xml.utf8)) let contents = root.allDescendants("Contents") XCTAssertEqual(contents.count, 2) XCTAssertEqual(contents[0].child("Key")?.text, "index.html") XCTAssertEqual(contents[0].child("Size")?.text, "2048") XCTAssertEqual(contents[1].child("Key")?.text, "css/style.css") } func testParseRoute53HostedZonesResponse() throws { let xml = """ /hostedzone/Z1234ABC example.com. 8 My domainfalse """ let root = try AWSXMLParser().parse(data: Data(xml.utf8)) let zones = root.allDescendants("HostedZone") XCTAssertEqual(zones.count, 1) XCTAssertEqual(zones[0].child("Id")?.text, "/hostedzone/Z1234ABC") XCTAssertEqual(zones[0].child("Name")?.text, "example.com.") XCTAssertEqual(zones[0].descendant("Comment")?.text, "My domain") } } // MARK: - AWS Error Tests final class AWSErrorTests: XCTestCase { func testAllErrorsHaveDescriptions() { let errors: [AWSError] = [ .notAuthenticated, .invalidURL("https://bad"), .invalidResponse, .apiError(statusCode: 403, body: "Forbidden"), .bucketNotFound("my-bucket"), .objectNotFound("file.txt"), .bucketAlreadyExists("existing"), .accessDenied("s3://bucket"), .invalidXML("parse error"), .deploymentFailed("timeout"), .distributionNotFound("EXYZ123"), .hostedZoneNotFound("example.com"), .functionNotFound("myFunc"), .timeout, ] for error in errors { XCTAssertNotNil(error.errorDescription, "\(error) should have a description") XCTAssertFalse(error.errorDescription!.isEmpty) } } func testNotAuthenticatedMessage() { let err = AWSError.notAuthenticated XCTAssertTrue(err.errorDescription!.contains("credential")) } func testApiErrorIncludesStatusCode() { let err = AWSError.apiError(statusCode: 500, body: "Internal Server Error") XCTAssertTrue(err.errorDescription!.contains("500")) XCTAssertTrue(err.errorDescription!.contains("Internal Server Error")) } func testApiErrorTruncatesLongBody() { let longBody = String(repeating: "x", count: 1000) let err = AWSError.apiError(statusCode: 400, body: longBody) // Should truncate to 500 chars XCTAssertTrue(err.errorDescription!.count < 600) } } // MARK: - AWS Service Credential Tests final class AWSServiceTests: XCTestCase { func testDefaultRegion() { let store = CredentialStore() let aws = AWSService(credentialStore: store) XCTAssertEqual(aws.defaultRegion, "us-east-1") } func testSetAndGetCredentials() { let store = CredentialStore() let aws = AWSService(credentialStore: store) aws.setCredentials(accessKeyId: "AKIA123", secretAccessKey: "secret", region: "eu-west-1") XCTAssertEqual(aws.accessKeyId, "AKIA123") XCTAssertEqual(aws.secretAccessKey, "secret") XCTAssertEqual(aws.defaultRegion, "eu-west-1") } func testIsAuthenticated() { let store = CredentialStore() let aws = AWSService(credentialStore: store) XCTAssertFalse(aws.isAuthenticated) aws.setCredentials(accessKeyId: "AKIA123", secretAccessKey: "secret") XCTAssertTrue(aws.isAuthenticated) } func testIsNotAuthenticatedWithEmpty() { let store = CredentialStore() let aws = AWSService(credentialStore: store) store.set("AWS_ACCESS_KEY_ID", value: "") store.set("AWS_SECRET_ACCESS_KEY", value: "") XCTAssertFalse(aws.isAuthenticated) } func testSessionToken() { let store = CredentialStore() let aws = AWSService(credentialStore: store) XCTAssertNil(aws.sessionToken) store.set("AWS_SESSION_TOKEN", value: "tok123") XCTAssertEqual(aws.sessionToken, "tok123") } func testRegionEnum() { XCTAssertEqual(AWSService.Region.usEast1.rawValue, "us-east-1") XCTAssertEqual(AWSService.Region.euCentral1.rawValue, "eu-central-1") XCTAssertEqual(AWSService.Region.allCases.count, 10) } } // MARK: - SHA256/Data Hex Extension Tests final class HexStringTests: XCTestCase { func testSHA256Hex() { let hash = SHA256.hash(data: Data("hello".utf8)) XCTAssertEqual(hash.hexString.count, 64) // SHA256 = 32 bytes = 64 hex chars XCTAssertEqual(hash.hexString, "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824") } func testDataHex() { let data = Data([0x00, 0xFF, 0xAB, 0x12]) XCTAssertEqual(data.hexString, "00ffab12") } func testEmptyDataHex() { XCTAssertEqual(Data().hexString, "") } } // MARK: - S3 Model Tests final class S3ModelTests: XCTestCase { func testS3ObjectFormattedSize() { XCTAssertEqual(S3Object(key: "a", size: 500, lastModified: nil, etag: nil).formattedSize, "500 B") XCTAssertTrue(S3Object(key: "b", size: 2048, lastModified: nil, etag: nil).formattedSize.contains("KB")) XCTAssertTrue(S3Object(key: "c", size: 5_242_880, lastModified: nil, etag: nil).formattedSize.contains("MB")) XCTAssertTrue(S3Object(key: "d", size: 2_147_483_648, lastModified: nil, etag: nil).formattedSize.contains("GB")) } func testS3DeployResult() { let result = S3DeployResult( success: true, bucket: "test-bucket", region: "us-east-1", uploadedFiles: ["index.html", "style.css"], errors: [], websiteURL: "http://test-bucket.s3-website-us-east-1.amazonaws.com", duration: 2.5 ) XCTAssertTrue(result.success) XCTAssertEqual(result.uploadedFiles.count, 2) XCTAssertTrue(result.errors.isEmpty) XCTAssertNotNil(result.websiteURL) } func testS3BucketModel() { let bucket = S3Bucket(name: "my-site", creationDate: "2026-01-01T00:00:00Z") XCTAssertEqual(bucket.name, "my-site") XCTAssertEqual(bucket.creationDate, "2026-01-01T00:00:00Z") } } // MARK: - Route 53 Model Tests final class Route53ModelTests: XCTestCase { func testHostedZoneDomainName() { let zone = Route53HostedZone(id: "Z123", name: "example.com.", recordCount: 5, comment: nil, isPrivate: false) XCTAssertEqual(zone.domainName, "example.com") // trailing dot removed } func testHostedZoneNoDot() { let zone = Route53HostedZone(id: "Z123", name: "example.com", recordCount: 5, comment: nil, isPrivate: false) XCTAssertEqual(zone.domainName, "example.com") } func testRecordSetIsAlias() { let alias = Route53AliasTarget(hostedZoneId: "Z2FD", dnsName: "d123.cloudfront.net", evaluateTargetHealth: false) let record = Route53RecordSet(name: "example.com.", type: "A", ttl: 300, values: [], aliasTarget: alias) XCTAssertTrue(record.isAlias) let normal = Route53RecordSet(name: "example.com.", type: "A", ttl: 300, values: ["1.2.3.4"], aliasTarget: nil) XCTAssertFalse(normal.isAlias) } } // MARK: - CloudFront Model Tests final class CloudFrontModelTests: XCTestCase { func testDistribution() { let dist = CloudFrontDistribution( id: "E1ABC", domainName: "d123.cloudfront.net", status: "Deployed", enabled: true, aliases: ["example.com"], origins: ["my-bucket.s3.amazonaws.com"] ) XCTAssertEqual(dist.id, "E1ABC") XCTAssertTrue(dist.enabled) XCTAssertEqual(dist.aliases, ["example.com"]) } func testACMCertificate() { let cert = ACMCertificate(arn: "arn:aws:acm:us-east-1:123:cert/abc", domainName: "example.com", status: "ISSUED") XCTAssertTrue(cert.arn.hasPrefix("arn:aws:acm")) XCTAssertEqual(cert.status, "ISSUED") } func testCertificateValidationRecord() { let rec = CertificateValidationRecord( domain: "example.com", recordName: "_abc.example.com.", recordValue: "_xyz.acm-validation.aws.", recordType: "CNAME" ) XCTAssertEqual(rec.recordType, "CNAME") XCTAssertTrue(rec.recordName.hasPrefix("_")) } } // MARK: - Lambda Model Tests final class LambdaModelTests: XCTestCase { func testRuntimeRawValues() { XCTAssertEqual(LambdaRuntime.nodejs20.rawValue, "nodejs20.x") XCTAssertEqual(LambdaRuntime.python312.rawValue, "python3.12") XCTAssertEqual(LambdaRuntime.dotnet8.rawValue, "dotnet8") XCTAssertEqual(LambdaRuntime.provided2023.rawValue, "provided.al2023") } func testRuntimeDisplayNames() { XCTAssertEqual(LambdaRuntime.nodejs20.displayName, "Node.js 20.x") XCTAssertEqual(LambdaRuntime.python312.displayName, "Python 3.12") XCTAssertEqual(LambdaRuntime.java21.displayName, "Java 21") } func testAllRuntimes() { XCTAssertEqual(LambdaRuntime.allCases.count, 10) } func testLambdaFunctionFormattedCodeSize() { let small = LambdaFunction(name: "f", arn: "", runtime: "nodejs20.x", handler: "index.handler", memorySize: 128, timeout: 3, lastModified: nil, codeSize: 500, description: nil) XCTAssertEqual(small.formattedCodeSize, "500 B") let medium = LambdaFunction(name: "f", arn: "", runtime: "nodejs20.x", handler: "index.handler", memorySize: 128, timeout: 3, lastModified: nil, codeSize: 5120, description: nil) XCTAssertTrue(medium.formattedCodeSize.contains("KB")) let large = LambdaFunction(name: "f", arn: "", runtime: "nodejs20.x", handler: "index.handler", memorySize: 128, timeout: 3, lastModified: nil, codeSize: 10_485_760, description: nil) XCTAssertTrue(large.formattedCodeSize.contains("MB")) } func testInvokeResult() { let result = LambdaInvokeResult(statusCode: 200, payload: "{\"message\":\"ok\"}", parsedPayload: ["message": "ok"]) XCTAssertEqual(result.statusCode, 200) XCTAssertTrue(result.payload.contains("ok")) } } // MARK: - Website Template Tests @MainActor final class WebsiteTemplateTests: XCTestCase { func testAllTemplatesExist() { XCTAssertEqual(WebsiteTemplate.allTemplates.count, 6) } func testTemplateIDs() { let ids = WebsiteTemplate.allTemplates.map(\.id) XCTAssertTrue(ids.contains("blank")) XCTAssertTrue(ids.contains("landing")) XCTAssertTrue(ids.contains("portfolio")) XCTAssertTrue(ids.contains("blog")) XCTAssertTrue(ids.contains("docs")) XCTAssertTrue(ids.contains("dashboard")) } func testBlankTemplateGeneratesFiles() { let files = WebsiteTemplate.blank.generateFiles("TestSite") let paths = files.map(\.path) XCTAssertTrue(paths.contains("index.html")) XCTAssertTrue(paths.contains("css/style.css")) XCTAssertTrue(paths.contains("js/main.js")) } func testBlankTemplateContainsProjectName() { let files = WebsiteTemplate.blank.generateFiles("My Cool Site") let html = files.first { $0.path == "index.html" }?.content ?? "" XCTAssertTrue(html.contains("My Cool Site")) } func testLandingTemplateFiles() { let files = WebsiteTemplate.landingPage.generateFiles("LandingSite") let paths = files.map(\.path) XCTAssertTrue(paths.contains("index.html")) XCTAssertTrue(paths.contains("css/style.css")) XCTAssertTrue(paths.contains("js/main.js")) XCTAssertGreaterThan(files.count, 3) } func testPortfolioTemplateFiles() { let files = WebsiteTemplate.portfolio.generateFiles("Portfolio") let paths = files.map(\.path) XCTAssertTrue(paths.contains("index.html")) XCTAssertTrue(paths.contains("projects.html")) } func testBlogTemplateFiles() { let files = WebsiteTemplate.blog.generateFiles("Blog") let paths = files.map(\.path) XCTAssertTrue(paths.contains("index.html")) XCTAssertTrue(paths.contains("post.html")) } func testDocsTemplateFiles() { let files = WebsiteTemplate.documentation.generateFiles("Docs") let paths = files.map(\.path) XCTAssertTrue(paths.contains("index.html")) } func testDashboardTemplateFiles() { let files = WebsiteTemplate.dashboard.generateFiles("Dashboard") let paths = files.map(\.path) XCTAssertTrue(paths.contains("index.html")) } func testAllTemplatesProduceValidHTML() { for template in WebsiteTemplate.allTemplates { let files = template.generateFiles("TestProject") let html = files.first { $0.path == "index.html" }?.content ?? "" XCTAssertTrue(html.contains(""), "\(template.id) should have closing html tag") } } func testTemplateCategories() { for template in WebsiteTemplate.allTemplates { XCTAssertFalse(template.category.isEmpty, "\(template.id) should have a category") XCTAssertFalse(template.description.isEmpty, "\(template.id) should have a description") } } func testWebsiteProjectInit() { let dir = URL(fileURLWithPath: "/tmp/test") let project = WebsiteProject(name: "site", directory: dir, template: "blank", createdAt: Date()) XCTAssertEqual(project.name, "site") XCTAssertEqual(project.template, "blank") XCTAssertNil(project.deployedURL) XCTAssertNil(project.domain) } } // MARK: - Website Deploy Provider Tests final class WebsiteDeployProviderTests: XCTestCase { func testAllProviders() { XCTAssertEqual(WebsiteDeployService.Provider.allCases.count, 5) } func testProviderDescriptions() { for provider in WebsiteDeployService.Provider.allCases { XCTAssertFalse(provider.description.isEmpty) } } func testProviderCLIRequirements() { XCTAssertEqual(WebsiteDeployService.Provider.netlify.requiresCLI, "netlify") XCTAssertEqual(WebsiteDeployService.Provider.surge.requiresCLI, "surge") XCTAssertEqual(WebsiteDeployService.Provider.vercel.requiresCLI, "vercel") XCTAssertEqual(WebsiteDeployService.Provider.githubPages.requiresCLI, "git") XCTAssertEqual(WebsiteDeployService.Provider.rsync.requiresCLI, "rsync") } func testDeployResultModel() { let result = WebsiteDeployService.DeployResult( success: true, url: "https://my-site.netlify.app", output: "Published", provider: .netlify, duration: 3.5 ) XCTAssertTrue(result.success) XCTAssertEqual(result.url, "https://my-site.netlify.app") XCTAssertEqual(result.provider, .netlify) } } // MARK: - GoDaddy Model Tests final class GoDaddyModelTests: XCTestCase { func testEnvironments() { XCTAssertTrue(GoDaddyService.Environment.ote.baseURL.contains("ote")) XCTAssertTrue(GoDaddyService.Environment.production.baseURL.contains("api.godaddy.com")) XCTAssertEqual(GoDaddyService.Environment.allCases.count, 2) } func testDNSRecordTypes() { XCTAssertEqual(DNSRecordType.A.rawValue, "A") XCTAssertEqual(DNSRecordType.CNAME.rawValue, "CNAME") XCTAssertEqual(DNSRecordType.MX.rawValue, "MX") XCTAssertEqual(DNSRecordType.TXT.rawValue, "TXT") } func testGoDaddyErrorDescriptions() { let errors: [GoDaddyError] = [ .notAuthenticated, .unauthorized, .forbidden, .notFound("example.com"), .rateLimited, .invalidURL("bad"), .invalidResponse, ] for error in errors { XCTAssertNotNil(error.errorDescription, "\(error) should have description") } } } // MARK: - AWSS3 Website Endpoint Tests final class AWSS3WebsiteTests: XCTestCase { func testWebsiteEndpoint() { let s3 = AWSS3Service() let url = s3.websiteEndpoint(bucket: "my-site", region: "us-east-1") XCTAssertEqual(url, "http://my-site.s3-website-us-east-1.amazonaws.com") } func testWebsiteEndpointOtherRegion() { let s3 = AWSS3Service() let url = s3.websiteEndpoint(bucket: "eu-site", region: "eu-west-1") XCTAssertEqual(url, "http://eu-site.s3-website-eu-west-1.amazonaws.com") } }