diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index db1a9eff2f..9ed843e7eb 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -74,6 +74,23 @@ jobs:
AZURECR_PAT: ${{ secrets.AZURECR_PAT }}
GOOGLECR_KEYS: ${{ secrets.GOOGLECR_KEYS }}
+ - name: E2E Tests
+ if: "contains(github.event.head_commit.message, '[release]') || contains(github.event.head_commit.message, '[e2e test]')"
+ run: |
+ make e2eTest
+ env:
+ GRADLE_OPTS: '-Dorg.gradle.daemon=false'
+ GITHUB_TOKEN: ${{ secrets.GH_SEQERA_TOKEN }}
+ AWS_ACCESS_KEY_ID: ${{secrets.TOWER_CI_AWS_ACCESS}}
+ AWS_SECRET_ACCESS_KEY: ${{secrets.TOWER_CI_AWS_SECRET}}
+ DOCKER_USER: ${{ secrets.DOCKER_USER }}
+ DOCKER_PAT: ${{ secrets.DOCKER_PAT }}
+ QUAY_USER: ${{ secrets.QUAY_USER }}
+ QUAY_PAT: ${{ secrets.QUAY_PAT }}
+ AZURECR_USER: ${{ secrets.AZURECR_USER }}
+ AZURECR_PAT: ${{ secrets.AZURECR_PAT }}
+ GOOGLECR_KEYS: ${{ secrets.GOOGLECR_KEYS }}
+
- name: Cleanup build workspace
if: always()
run: |
@@ -98,7 +115,8 @@ jobs:
with:
name: test-reports-jdk-${{ matrix.java_version }}
path: |
- **/build/reports/tests/test
+ **/build/reports/tests
+
- name : Publish code coverage report
if: success()
diff --git a/Makefile b/Makefile
index ab044536b1..d5efcd3551 100644
--- a/Makefile
+++ b/Makefile
@@ -13,6 +13,9 @@ compile:
check:
./gradlew check
+e2eTest:
+ ./gradlew e2eTest
+
image:
./gradlew jibDockerBuild
diff --git a/build.gradle b/build.gradle
index 4f35e4803d..fbaf5d27f2 100644
--- a/build.gradle
+++ b/build.gradle
@@ -224,3 +224,29 @@ jacocoTestReport {
}))
}
}
+
+configurations {
+ e2eTestCompileOnly.extendsFrom testCompileOnly, compileOnly
+ e2eTestImplementation.extendsFrom testImplementation, implementation
+ e2eTestRuntimeOnly.extendsFrom testRuntimeOnly, runtimeOnly
+}
+
+sourceSets {
+ e2eTest {
+ java {
+ srcDirs = ['src/e2eTest/groovy']
+ }
+ resources {
+ srcDirs = ['src/e2eTest/resources']
+ }
+ compileClasspath += sourceSets.main.output + sourceSets.test.output
+ runtimeClasspath += sourceSets.main.output + sourceSets.test.output
+ }
+}
+
+tasks.register('e2eTest', Test) {
+ group = 'verification'
+ testClassesDirs = sourceSets.e2eTest.output.classesDirs
+ classpath = sourceSets.e2eTest.runtimeClasspath
+ shouldRunAfter test
+}
diff --git a/src/e2eTest/groovy/io/seqera/wave/controller/ContainerControllerHttpE2ETest.groovy b/src/e2eTest/groovy/io/seqera/wave/controller/ContainerControllerHttpE2ETest.groovy
new file mode 100644
index 0000000000..5c6c51acb9
--- /dev/null
+++ b/src/e2eTest/groovy/io/seqera/wave/controller/ContainerControllerHttpE2ETest.groovy
@@ -0,0 +1,137 @@
+/*
+ * Wave, containers provisioning service
+ * Copyright (c) 2024, Seqera Labs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package io.seqera.wave.controller
+
+import spock.lang.Specification
+import io.micronaut.context.annotation.Property
+import io.micronaut.http.HttpRequest
+import io.micronaut.http.client.HttpClient
+import io.micronaut.http.client.annotation.Client
+import io.micronaut.objectstorage.InputStreamMapper
+import io.micronaut.objectstorage.ObjectStorageOperations
+import io.micronaut.objectstorage.aws.AwsS3Configuration
+import io.micronaut.objectstorage.aws.AwsS3Operations
+import io.micronaut.test.annotation.MockBean
+import io.micronaut.test.extensions.spock.annotation.MicronautTest
+import io.seqera.wave.api.BuildStatusResponse
+import io.seqera.wave.api.PackagesSpec
+import io.seqera.wave.api.SubmitContainerTokenRequest
+import io.seqera.wave.api.SubmitContainerTokenResponse
+import io.seqera.wave.core.RouteHandler
+import io.seqera.wave.service.pairing.PairingService
+import io.seqera.wave.service.pairing.PairingServiceImpl
+import io.seqera.wave.test.AwsS3TestContainer
+import io.seqera.wave.tower.client.TowerClient
+import jakarta.inject.Inject
+import jakarta.inject.Named
+import software.amazon.awssdk.auth.credentials.AwsBasicCredentials
+import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider
+import software.amazon.awssdk.regions.Region
+import software.amazon.awssdk.services.s3.S3Client
+
+/**
+ * @author Munish Chouhan
+ */
+@MicronautTest
+@Property(name = 'wave.build.logs.bucket', value = 'test-bucket')
+class ContainerControllerHttpE2ETest extends Specification implements AwsS3TestContainer {
+
+ @Inject
+ @Client("/")
+ HttpClient httpClient
+
+ @Inject
+ PairingService pairingService
+
+ @Inject
+ TowerClient towerClient
+
+ @Inject
+ RouteHandler routeHandler
+
+ @MockBean(PairingServiceImpl)
+ PairingService mockPairingService(){
+ Mock(PairingService)
+ }
+
+ @MockBean(TowerClient)
+ TowerClient mockTowerClient() {
+ Mock(TowerClient)
+ }
+
+ def s3Client = S3Client.builder()
+ .endpointOverride(URI.create("http://${awsS3HostName}:${awsS3Port}"))
+ .region(Region.EU_WEST_1)
+ .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create("accesskey", "secretkey")))
+ .forcePathStyle(true)
+ .build()
+
+ @MockBean(ObjectStorageOperations)
+ @Named('build-logs')
+ ObjectStorageOperations mockObjectStorageOperations() {
+ AwsS3Configuration configuration = new AwsS3Configuration('build-logs')
+ configuration.setBucket("test-bucket")
+ return new AwsS3Operations(configuration, s3Client, Mock(InputStreamMapper))
+ }
+
+ def 'should build conda image then store conda lockfile and fetch conda lockfile' () {
+ given:
+ def request = new SubmitContainerTokenRequest(
+ packages: new PackagesSpec(channels: ['conda-forge'], entries: ['xz'], type: 'CONDA'),
+ buildRepository: "test/repository",
+ cacheRepository: "test/cache"
+
+ )
+ and:
+ s3Client.createBucket { it.bucket("test-bucket") }
+
+ when:
+ def res = httpClient
+ .toBlocking()
+ .exchange(HttpRequest.POST("/v1alpha2/container",request), SubmitContainerTokenResponse).body()
+ and:
+ awaitBuild(res.buildId)
+ and:
+ res = httpClient
+ .toBlocking()
+ .exchange(HttpRequest.GET("/v1alpha1/builds/$res.buildId/condalock"), String).body()
+
+ then:
+ res.contains('conda create --name --file ')
+ }
+
+ boolean awaitBuild(String buildId) {
+ long startTime = System.currentTimeMillis()
+ long timeout = 120000
+ long checkInterval = 5000
+ while (System.currentTimeMillis() - startTime < timeout) {
+ def res = httpClient
+ .toBlocking()
+ .exchange(HttpRequest.GET("/v1alpha1/builds/$buildId/status"), BuildStatusResponse)
+ .body()
+
+ if (res.status == BuildStatusResponse.Status.COMPLETED) {
+ return true
+ }
+ sleep checkInterval
+ }
+
+ return false
+ }
+}
diff --git a/src/e2eTest/resources/application-test.yml b/src/e2eTest/resources/application-test.yml
new file mode 100644
index 0000000000..71dfec879b
--- /dev/null
+++ b/src/e2eTest/resources/application-test.yml
@@ -0,0 +1,73 @@
+micronaut:
+ server:
+ # Use a random port for testing to enable running tests with the application running as well as parallel execution
+ port: -1
+ http:
+ client:
+ read-timeout: 90s
+ max-content-length: 20Mb
+ codec:
+ json:
+ additionalTypes:
+ - application/vnd.docker.distribution.manifest.list.v2+json
+ object-storage:
+ aws:
+ build-logs:
+ bucket: "${wave.build.logs.bucket}"
+---
+datasources:
+ default:
+ url: "jdbc:h2:mem:test_mem"
+ driverClassName: "org.h2.Driver"
+ username: "sq"
+ password: ""
+ dialect: H2
+ schema-generate: CREATE_DROP
+---
+wave:
+ accounts:
+ foo: "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
+ bar: "486ea46224d1bb4fb680f34f7c9ad96a8f24ec88be73ea8e5a6c65260e9cb8a7"
+ username: "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8"
+ registries:
+ default: docker.io
+ docker.io:
+ username: ${DOCKER_USER:test}
+ password: ${DOCKER_PAT:test}
+ quay.io:
+ username: ${QUAY_USER:test}
+ password: ${QUAY_PAT:test}
+ 195996028523.dkr.ecr.eu-west-1.amazonaws.com:
+ username: ${AWS_ACCESS_KEY_ID:test}
+ password: ${AWS_SECRET_ACCESS_KEY:test}
+ public.ecr.aws:
+ username: ${AWS_ACCESS_KEY_ID:test}
+ password: ${AWS_SECRET_ACCESS_KEY:test}
+ seqeralabs.azurecr.io:
+ username: ${AZURECR_USER:test}
+ password: ${AZURECR_PAT:test}
+ europe-southwest1-docker.pkg.dev:
+ credentials : ${GOOGLECR_KEYS:test}
+ quay.io/test/public/repo:
+ username: 'foo'
+ password: 'bar'
+ build:
+ workspace: 'build-workspace'
+ logs :
+ enabled : true
+ bucket : 'nextflow-ci'
+ prefix : 'wave-build/logs'
+ conda-lock-prefix: 'wave-build/conda-locks'
+ scan:
+ enabled: true
+---
+logger:
+ levels:
+ io.micronaut.data.query: "DEBUG"
+---
+redis :
+ pool :
+ enabled : false
+ health:
+ enabled: false
+---