1af1db7ffc
- AWSService: SigV4 request signing, XML parser, credential management - AWSS3Service: bucket/object CRUD, website hosting, deploy pipeline - AWSRoute53Service: hosted zones, DNS records, S3/CloudFront aliases - AWSCloudFrontService: distributions, cache invalidation, ACM certs - AWSLambdaService: functions, invoke, zip deploy - AWSTools: 15 MCP agent tools for all AWS operations - CredentialStore: AWS credential convenience properties - AWSAndWebsiteTests: 72 tests covering models, XML parsing, errors, credentials, templates, deploy providers (165 total tests passing)
693 lines
25 KiB
Swift
693 lines
25 KiB
Swift
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 = "<Root><Name>hello</Name></Root>"
|
|
let root = try AWSXMLParser().parse(data: Data(xml.utf8))
|
|
XCTAssertEqual(root.name, "Root")
|
|
XCTAssertEqual(root.child("Name")?.text, "hello")
|
|
}
|
|
|
|
func testParseNestedElements() throws {
|
|
let xml = """
|
|
<Response>
|
|
<Buckets>
|
|
<Bucket><Name>bucket-a</Name></Bucket>
|
|
<Bucket><Name>bucket-b</Name></Bucket>
|
|
</Buckets>
|
|
</Response>
|
|
"""
|
|
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 = """
|
|
<A><B><C><D>deep</D></C></B></A>
|
|
"""
|
|
let root = try AWSXMLParser().parse(data: Data(xml.utf8))
|
|
XCTAssertEqual(root.descendant("D")?.text, "deep")
|
|
XCTAssertNil(root.descendant("Missing"))
|
|
}
|
|
|
|
func testAllDescendants() throws {
|
|
let xml = """
|
|
<Root>
|
|
<Items><Value>1</Value></Items>
|
|
<Items><Value>2</Value></Items>
|
|
<Nested><Items><Value>3</Value></Items></Nested>
|
|
</Root>
|
|
"""
|
|
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 = "<A><B>direct</B><C><B>nested</B></C></A>"
|
|
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 = "<Root><Empty></Empty></Root>"
|
|
let root = try AWSXMLParser().parse(data: Data(xml.utf8))
|
|
XCTAssertEqual(root.child("Empty")?.text, "")
|
|
}
|
|
|
|
func testInvalidXMLThrows() {
|
|
let bad = "<Unclosed>"
|
|
XCTAssertThrowsError(try AWSXMLParser().parse(data: Data(bad.utf8)))
|
|
}
|
|
|
|
func testParseS3ListBucketsResponse() throws {
|
|
let xml = """
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
<ListAllMyBucketsResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
|
<Owner><ID>owner123</ID></Owner>
|
|
<Buckets>
|
|
<Bucket>
|
|
<Name>my-website</Name>
|
|
<CreationDate>2026-01-15T10:30:00.000Z</CreationDate>
|
|
</Bucket>
|
|
<Bucket>
|
|
<Name>my-backups</Name>
|
|
<CreationDate>2026-03-20T14:00:00.000Z</CreationDate>
|
|
</Bucket>
|
|
</Buckets>
|
|
</ListAllMyBucketsResult>
|
|
"""
|
|
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 = """
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
|
<Name>my-site</Name>
|
|
<Contents>
|
|
<Key>index.html</Key>
|
|
<Size>2048</Size>
|
|
<LastModified>2026-04-01T12:00:00.000Z</LastModified>
|
|
<ETag>"abc123"</ETag>
|
|
</Contents>
|
|
<Contents>
|
|
<Key>css/style.css</Key>
|
|
<Size>512</Size>
|
|
<LastModified>2026-04-01T12:00:01.000Z</LastModified>
|
|
<ETag>"def456"</ETag>
|
|
</Contents>
|
|
</ListBucketResult>
|
|
"""
|
|
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 = """
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
<ListHostedZonesResponse>
|
|
<HostedZones>
|
|
<HostedZone>
|
|
<Id>/hostedzone/Z1234ABC</Id>
|
|
<Name>example.com.</Name>
|
|
<ResourceRecordSetCount>8</ResourceRecordSetCount>
|
|
<Config><Comment>My domain</Comment><PrivateZone>false</PrivateZone></Config>
|
|
</HostedZone>
|
|
</HostedZones>
|
|
</ListHostedZonesResponse>
|
|
"""
|
|
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("<!DOCTYPE html") || html.contains("<!doctype html"),
|
|
"\(template.id) should produce valid HTML")
|
|
XCTAssertTrue(html.contains("</html>"),
|
|
"\(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")
|
|
}
|
|
}
|