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
64 changes: 36 additions & 28 deletions Tests/ContainerRegistryTests/AuthTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,79 +12,87 @@
//
//===----------------------------------------------------------------------===//

import Foundation
import Basics
import XCTest
import Foundation
import Testing

class AuthTests: XCTestCase, @unchecked Sendable {
struct AuthTests {
// SwiftPM's NetrcAuthorizationProvider does not throw an error if the .netrc file
// does not exist. For simplicity the local vendored version does the same.
func testNonexistentNetrc() async throws {
@Test func testNonexistentNetrc() async throws {
// Construct a URL to a nonexistent file in the bundle directory
let netrcURL = Bundle.module.resourceURL!.appendingPathComponent("netrc.nonexistent")
XCTAssertFalse(FileManager.default.fileExists(atPath: netrcURL.path))
#expect(!FileManager.default.fileExists(atPath: netrcURL.path))

let authProvider = try NetrcAuthorizationProvider(netrcURL)
XCTAssertNil(authProvider.authentication(for: URL(string: "https://hub.example.com")!))
#expect(authProvider.authentication(for: URL(string: "https://hub.example.com")!) == nil)
}

func testEmptyNetrc() async throws {
@Test func testEmptyNetrc() async throws {
let netrcURL = Bundle.module.url(forResource: "netrc", withExtension: "empty")!
let authProvider = try NetrcAuthorizationProvider(netrcURL)
XCTAssertNil(authProvider.authentication(for: URL(string: "https://hub.example.com")!))
#expect(authProvider.authentication(for: URL(string: "https://hub.example.com")!) == nil)
}

func testBasicNetrc() async throws {
@Test func testBasicNetrc() async throws {
let netrcURL = Bundle.module.url(forResource: "netrc", withExtension: "basic")!
let authProvider = try NetrcAuthorizationProvider(netrcURL)
XCTAssertNil(authProvider.authentication(for: URL(string: "https://nothing.example.com")!))
#expect(authProvider.authentication(for: URL(string: "https://nothing.example.com")!) == nil)

guard let (user, password) = authProvider.authentication(for: URL(string: "https://hub.example.com")!) else {
return XCTFail("Expected to find a username and password")
Issue.record("Expected to find a username and password")
return
}
XCTAssertEqual(user, "swift")
XCTAssertEqual(password, "password")
#expect(user == "swift")
#expect(password == "password")
}

// The default entry is used if no specific entry matches
func testComplexNetrcWithDefault() async throws {
@Test func testComplexNetrcWithDefault() async throws {
let netrcURL = Bundle.module.url(forResource: "netrc", withExtension: "default")!
let authProvider = try NetrcAuthorizationProvider(netrcURL)

guard let (user, password) = authProvider.authentication(for: URL(string: "https://nothing.example.com")!)
else { return XCTFail("Expected to find a username and password") }
XCTAssertEqual(user, "defaultlogin")
XCTAssertEqual(password, "defaultpassword")
else {
Issue.record("Expected to find a username and password")
return
}
#expect(user == "defaultlogin")
#expect(password == "defaultpassword")
}

// The default entry must be last in the file
func testComplexNetrcWithInvalidDefault() async throws {
@Test func testComplexNetrcWithInvalidDefault() async throws {
let netrcURL = Bundle.module.url(forResource: "netrc", withExtension: "invaliddefault")!
XCTAssertThrowsError(try NetrcAuthorizationProvider(netrcURL)) { error in
XCTAssertEqual(error as! NetrcError, NetrcError.invalidDefaultMachinePosition)
#expect { try NetrcAuthorizationProvider(netrcURL) } throws: { error in
error as! NetrcError == NetrcError.invalidDefaultMachinePosition
}
}

// If there are multiple entries for the same host, the last one wins
func testComplexNetrcOverriddenEntry() async throws {
@Test func testComplexNetrcOverriddenEntry() async throws {
let netrcURL = Bundle.module.url(forResource: "netrc", withExtension: "default")!
let authProvider = try NetrcAuthorizationProvider(netrcURL)

guard let (user, password) = authProvider.authentication(for: URL(string: "https://hub.example.com")!) else {
return XCTFail("Expected to find a username and password")
Issue.record("Expected to find a username and password")
return
}
XCTAssertEqual(user, "swift2")
XCTAssertEqual(password, "password2")
#expect(user == "swift2")
#expect(password == "password2")
}

// A singleton entry in a netrc file with defaults and overriden entries continues to work as in the simple case
func testComplexNetrcSingletonEntry() async throws {
@Test func testComplexNetrcSingletonEntry() async throws {
let netrcURL = Bundle.module.url(forResource: "netrc", withExtension: "default")!
let authProvider = try NetrcAuthorizationProvider(netrcURL)

guard let (user, password) = authProvider.authentication(for: URL(string: "https://another.example.com")!)
else { return XCTFail("Expected to find a username and password") }
XCTAssertEqual(user, "anotherlogin")
XCTAssertEqual(password, "anotherpassword")
else {
Issue.record("Expected to find a username and password")
return
}
#expect(user == "anotherlogin")
#expect(password == "anotherpassword")
}
}
55 changes: 27 additions & 28 deletions Tests/ContainerRegistryTests/ImageReferenceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
//===----------------------------------------------------------------------===//

@testable import ContainerRegistry
import XCTest
import Testing

struct ReferenceTest {
var reference: String
Expand All @@ -26,7 +26,7 @@ struct ReferenceTestCase {
var expected: ImageReference?
}

class ReferenceTests: XCTestCase {
struct ReferenceTests {
let tests = [
// A reference which does not contain a '/' is always interpreted as a repository name
// in the default registry.
Expand Down Expand Up @@ -106,60 +106,59 @@ class ReferenceTests: XCTestCase {
),
]

func testReferences() throws {
@Test func testReferences() throws {
for test in tests {
let parsed = try! ImageReference(fromString: test.reference, defaultRegistry: "default")
XCTAssertEqual(
parsed,
test.expected,
#expect(
parsed == test.expected,
"\(String(reflecting: parsed)) is not equal to \(String(reflecting: test.expected))"
)
}
}

func testLibraryReferences() throws {
@Test func testLibraryReferences() throws {
// docker.io is a special case, as references such as "swift:slim" with no registry component are translated to "docker.io/library/swift:slim"
// Verified against the behaviour of the docker CLI client

// Fully-qualified name splits as usual
XCTAssertEqual(
try! ImageReference(fromString: "docker.io/library/swift:slim", defaultRegistry: "docker.io"),
ImageReference(registry: "index.docker.io", repository: "library/swift", reference: "slim")
#expect(
try! ImageReference(fromString: "docker.io/library/swift:slim", defaultRegistry: "docker.io")
== ImageReference(registry: "index.docker.io", repository: "library/swift", reference: "slim")
)

// A repository with no '/' part is assumed to be `library`
XCTAssertEqual(
try! ImageReference(fromString: "docker.io/swift:slim", defaultRegistry: "docker.io"),
ImageReference(registry: "index.docker.io", repository: "library/swift", reference: "slim")
#expect(
try! ImageReference(fromString: "docker.io/swift:slim", defaultRegistry: "docker.io")
== ImageReference(registry: "index.docker.io", repository: "library/swift", reference: "slim")
)

// Parsing with 'docker.io' as default registry is the same as the fully qualified case
XCTAssertEqual(
try! ImageReference(fromString: "library/swift:slim", defaultRegistry: "docker.io"),
ImageReference(registry: "index.docker.io", repository: "library/swift", reference: "slim")
#expect(
try! ImageReference(fromString: "library/swift:slim", defaultRegistry: "docker.io")
== ImageReference(registry: "index.docker.io", repository: "library/swift", reference: "slim")
)

// Bare image name with no registry or repository is interpreted as being in docker.io/library when default is docker.io
XCTAssertEqual(
try! ImageReference(fromString: "swift:slim", defaultRegistry: "docker.io"),
ImageReference(registry: "index.docker.io", repository: "library/swift", reference: "slim")
#expect(
try! ImageReference(fromString: "swift:slim", defaultRegistry: "docker.io")
== ImageReference(registry: "index.docker.io", repository: "library/swift", reference: "slim")
)

// The minimum reference to a library image. No tag implies `latest`
XCTAssertEqual(
try! ImageReference(fromString: "swift", defaultRegistry: "docker.io"),
ImageReference(registry: "index.docker.io", repository: "library/swift", reference: "latest")
#expect(
try! ImageReference(fromString: "swift", defaultRegistry: "docker.io")
== ImageReference(registry: "index.docker.io", repository: "library/swift", reference: "latest")
)

// If the registry is not docker.io, the special case logic for `library` does not apply
XCTAssertEqual(
try! ImageReference(fromString: "localhost:5000/swift", defaultRegistry: "docker.io"),
ImageReference(registry: "localhost:5000", repository: "swift", reference: "latest")
#expect(
try! ImageReference(fromString: "localhost:5000/swift", defaultRegistry: "docker.io")
== ImageReference(registry: "localhost:5000", repository: "swift", reference: "latest")
)

XCTAssertEqual(
try! ImageReference(fromString: "swift", defaultRegistry: "localhost:5000"),
ImageReference(registry: "localhost:5000", repository: "swift", reference: "latest")
#expect(
try! ImageReference(fromString: "swift", defaultRegistry: "localhost:5000")
== ImageReference(registry: "localhost:5000", repository: "swift", reference: "latest")
)
}
}
57 changes: 28 additions & 29 deletions Tests/ContainerRegistryTests/SmokeTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,23 @@

import Foundation
import ContainerRegistry
import XCTest
import Testing

class SmokeTests: XCTestCase, @unchecked Sendable {
struct SmokeTests {
// These are basic tests to exercise the main registry operations.
// The tests assume that a fresh, empty registry instance is available at
// http://$REGISTRY_HOST:$REGISTRY_PORT

var client: RegistryClient!
var client: RegistryClient
let registryHost = ProcessInfo.processInfo.environment["REGISTRY_HOST"] ?? "localhost"
let registryPort = ProcessInfo.processInfo.environment["REGISTRY_PORT"] ?? "5000"

/// Registry client fixture created for each test
override func setUp() async throws {
try await super.setUp()
init() async throws {
client = try await RegistryClient(registry: "\(registryHost):\(registryPort)", insecure: true)
}

func testGetTags() async throws {
@Test func testGetTags() async throws {
let repository = "testgettags"

// registry:2 does not validate the contents of the config or image blobs
Expand All @@ -45,7 +44,7 @@ class SmokeTests: XCTestCase, @unchecked Sendable {
// Initially there will be no tags
do {
_ = try await client.getTags(repository: repository)
XCTFail("Getting tags for an untagged blob should have thrown an error")
Issue.record("Getting tags for an untagged blob should have thrown an error")
} catch {
// Expect to receive an error
}
Expand All @@ -61,7 +60,7 @@ class SmokeTests: XCTestCase, @unchecked Sendable {
// After setting a tag, we should be able to retrieve it
let _ = try await client.putManifest(repository: repository, reference: "latest", manifest: test_manifest)
let firstTag = try await client.getTags(repository: repository).tags.sorted()
XCTAssertEqual(firstTag, ["latest"])
#expect(firstTag == ["latest"])

// After setting another tag, the original tag should still exist
let _ = try await client.putManifest(
Expand All @@ -70,47 +69,47 @@ class SmokeTests: XCTestCase, @unchecked Sendable {
manifest: test_manifest
)
let secondTag = try await client.getTags(repository: repository)
XCTAssertEqual(secondTag.tags.sorted(), ["additional_tag", "latest"].sorted())
#expect(secondTag.tags.sorted() == ["additional_tag", "latest"].sorted())
}

func testGetNonexistentBlob() async throws {
@Test func testGetNonexistentBlob() async throws {
let repository = "testgetnonexistentblob"

do {
let _ = try await client.getBlob(
repository: repository,
digest: "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
)
XCTFail("should have thrown")
Issue.record("should have thrown")
} catch {}
}

func testCheckNonexistentBlob() async throws {
@Test func testCheckNonexistentBlob() async throws {
let repository = "testchecknonexistentblob"

let exists = try await client.blobExists(
repository: repository,
digest: "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
)
XCTAssertFalse(exists)
#expect(!exists)
}

func testPutAndGetBlob() async throws {
@Test func testPutAndGetBlob() async throws {
let repository = "testputandgetblob" // repository name must be lowercase

let blob_data = "test".data(using: .utf8)!

let descriptor = try await client.putBlob(repository: repository, data: blob_data)
XCTAssertEqual(descriptor.digest, "sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08")
#expect(descriptor.digest == "sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08")

let exists = try await client.blobExists(repository: repository, digest: descriptor.digest)
XCTAssertTrue(exists)
#expect(exists)

let blob = try await client.getBlob(repository: repository, digest: descriptor.digest)
XCTAssertEqual(blob, blob_data)
#expect(blob == blob_data)
}

func testPutAndGetTaggedManifest() async throws {
@Test func testPutAndGetTaggedManifest() async throws {
let repository = "testputandgettaggedmanifest" // repository name must be lowercase

// registry:2 does not validate the contents of the config or image blobs
Expand Down Expand Up @@ -139,13 +138,13 @@ class SmokeTests: XCTestCase, @unchecked Sendable {
let _ = try await client.putManifest(repository: repository, reference: "latest", manifest: test_manifest)

let manifest = try await client.getManifest(repository: repository, reference: "latest")
XCTAssertEqual(manifest.schemaVersion, 2)
XCTAssertEqual(manifest.config.mediaType, "application/vnd.docker.container.image.v1+json")
XCTAssertEqual(manifest.layers.count, 1)
XCTAssertEqual(manifest.layers[0].mediaType, "application/vnd.docker.image.rootfs.diff.tar.gzip")
#expect(manifest.schemaVersion == 2)
#expect(manifest.config.mediaType == "application/vnd.docker.container.image.v1+json")
#expect(manifest.layers.count == 1)
#expect(manifest.layers[0].mediaType == "application/vnd.docker.image.rootfs.diff.tar.gzip")
}

func testPutAndGetAnonymousManifest() async throws {
@Test func testPutAndGetAnonymousManifest() async throws {
let repository = "testputandgetanonymousmanifest" // repository name must be lowercase

// registry:2 does not validate the contents of the config or image blobs
Expand Down Expand Up @@ -178,13 +177,13 @@ class SmokeTests: XCTestCase, @unchecked Sendable {
)

let manifest = try await client.getManifest(repository: repository, reference: test_manifest.digest)
XCTAssertEqual(manifest.schemaVersion, 2)
XCTAssertEqual(manifest.config.mediaType, "application/vnd.docker.container.image.v1+json")
XCTAssertEqual(manifest.layers.count, 1)
XCTAssertEqual(manifest.layers[0].mediaType, "application/vnd.docker.image.rootfs.diff.tar.gzip")
#expect(manifest.schemaVersion == 2)
#expect(manifest.config.mediaType == "application/vnd.docker.container.image.v1+json")
#expect(manifest.layers.count == 1)
#expect(manifest.layers[0].mediaType == "application/vnd.docker.image.rootfs.diff.tar.gzip")
}

func testPutAndGetImageConfiguration() async throws {
@Test func testPutAndGetImageConfiguration() async throws {
let repository = "testputandgetimageconfiguration" // repository name must be lowercase

let configuration = ImageConfiguration(
Expand All @@ -205,6 +204,6 @@ class SmokeTests: XCTestCase, @unchecked Sendable {
digest: config_descriptor.digest
)

XCTAssertEqual(configuration, downloaded)
#expect(configuration == downloaded)
}
}
Loading
Loading