Skip to content

Commit

Permalink
Create integration test configuration (#264)
Browse files Browse the repository at this point in the history
* Create integration test configuration

* Temporarily enable GitHub action for pull requests

* Include service account identifier

* Roll back action setup to clone of pre-merge

* Remove application default config

* Collapse integration tests into single execution

* haha yes it works now

* Update script from comments.js

* Create debug step to containerize

* Use block scalar with chomping

* Include Git sample

* Echo several lines of text

* Echo command output

* Roll back to previous version

* Disable Terraform plan

* Restore gradle build action step

* Restore conditions and remove unnecessary steps

* Restore Google Cloud Auth step

* Include debugging for comment deletion step

* Use for over forEach for enclosed await

* Revise github actions workflow

* Roll back comment changes

* Fix variable name

* Delete all existing comments

* Log user state

* Print the whole thing

* once more, with data

* How much longer must this go on?

* Iterate over list items instead of keys

* Adjust scripts, deletion behaviour

* Include exports reference

* Correct javascript lambda

* Include javascript in spotless configuration

* Trigger build

* Include backticks for string literal

* Remove indentation indicator

* Formatting changes

* Restore debug trigger for integration test

* Further logging for comments filter

* Moar comments

* Asynchronous commands are asynchronous

* Logs again...

* Boolean not actually assigned to value

* Simplify condition

* Include return statement

* Linespace chomping and console logs

* How many times should i continue to make this mistake

* Force rerun of test task

* Remove test trigger, disable cache for integration

---------

Co-authored-by: Davies Ashley <[email protected]>
  • Loading branch information
ashdavies and ashdavies authored May 11, 2023
1 parent e4e7e2b commit ce2c577
Show file tree
Hide file tree
Showing 13 changed files with 236 additions and 203 deletions.
13 changes: 5 additions & 8 deletions .github/workflows/google-cloud.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,19 +85,16 @@ jobs:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const comments = require('./.github/workflows/scripts/comments.js');
const comment = await comments.get(context, github);
const fs = require('fs').promises;
await comments.deleteAll(context, github, it => it.body.startsWith("Terraform"));
const start = "Terraform will perform the following actions:"
if (comment && comment.body.startsWith(start)) {
await comments.delete(context, github, comment.id)
}
const fs = require('fs').promises;
const path = "${{ steps.plan.outputs.build-log }}"
const content = await fs.readFile(path, 'utf8')
const body = content.substring(content.indexOf(start), content.indexOf("───"))
await comments.create(context, github, content)
await comments.create(context, github, content ? content : "Build log empty")
- name: Containerize Cloud Run
env:
Expand All @@ -109,7 +106,7 @@ jobs:
if: github.ref == 'refs/heads/main'
run: |
./gradlew cloud-run:jib \
--image=europe-west1-docker.pkg.dev/${{ steps.google_cloud_auth.outputs.project_id }}/cloud-run-source-deploy/playground.ashdavies.dev \
--image=europe-west1-docker.pkg.dev/${{ steps.google_cloud_auth.outputs.project_id }}/cloud-run-source-deploy/playground.ashdavies.dev \
--no-configuration-cache \
--console=plain \
--info
Expand Down
68 changes: 68 additions & 0 deletions .github/workflows/integration-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: Integration Tests

on:
workflow_dispatch:
inputs:
debug:
description: 'Debug'
required: false
default: false
type: boolean

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
build:
runs-on: ubuntu-latest

permissions:
id-token: 'write'

steps:
- name: Setup JDK
uses: actions/setup-java@v3
with:
distribution: temurin
java-version: 17

- name: Checkout Repository
uses: actions/checkout@v3
with:
fetch-depth: 0

- name: Google Cloud Auth
id: google_cloud_auth
uses: google-github-actions/auth@v1
with:
workload_identity_provider: ${{ secrets.GOOGLE_WORKLOAD_IDENTITY }}
service_account: ${{ secrets.GOOGLE_SERVICE_ACCOUNT_ID }}
token_format: access_token

- name: Run Gradle Tasks
id: gradle_build
uses: gradle/gradle-build-action@v2
env:
GOOGLE_PROJECT_API_KEY: ${{ secrets.google_project_api_key }}
GOOGLE_SERVICE_ACCOUNT_ID: ${{ secrets.GOOGLE_SERVICE_ACCOUNT_ID }}
MOBILE_SDK_APP_ID: ${{ secrets.mobile_sdk_app_id }}
PLAYGROUND_API_KEY: ${{ secrets.PLAYGROUND_API_KEY }}
with:
gradle-home-cache-cleanup: true
arguments: >
cloud-run:integrationTest
${{ inputs.debug && '--debug' || '--info' }}
--console=plain
- name: Produce Test Summary
uses: test-summary/action@v2
with:
paths: "**/build/test-results/test/TEST-*.xml"

- name: Upload Test Report
uses: actions/upload-artifact@v3
with:
name: test-report
path: "**/build/reports/tests"
if-no-files-found: error
27 changes: 13 additions & 14 deletions .github/workflows/pre-merge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,32 +68,31 @@ jobs:
PLAYGROUND_API_KEY: ${{ secrets.PLAYGROUND_API_KEY }}
with:
gradle-home-cache-cleanup: true
arguments: |
arguments: >
build
${{ (contains(github.event.pull_request.labels.*.name, 'Dry Run') && '--dry-run' || '') }}
--console=plain
--info
- name: Gradle Scan Link
- name: Delete Bot Comments
uses: actions/github-script@v6
if: ${{ github.event_name == 'pull_request' && steps.gradle.outputs.build-scan-url }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const comments = require('./.github/workflows/scripts/comments.js');
const comment = await comments.get(context, github);
const message = "Build scan published to";
const url = /https:\/\/gradle\.com\/s\/\w+/;
const pattern = new RegExp(`${message} ${url.source}`);
await comments.deleteAll(context, github)
if (comment && comment.body.match(pattern)) {
await comments.delete(context, github, comment.id)
}
const body = `${message} ${{ steps.gradle.outputs.build-scan-url }}`
await comments.create(context, github, body)
- name: Gradle Scan Link
uses: actions/github-script@v6
if: ${{ github.event_name == 'pull_request' && steps.gradle.outputs.build-scan-url }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const comments = require('./.github/workflows/scripts/comments.js');
const url = `${{ steps.gradle.outputs.build-scan-url }}`;
const message = `Build scan published to ${url}`;
await comments.create(context, github, message);
- name: Produce Test Summary
uses: test-summary/action@v2
Expand Down
16 changes: 12 additions & 4 deletions .github/workflows/scripts/comments.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
exports.get = async function (context, github) {
exports.findAll = async function (context, github, predicate = (it) => true) {
const comments = await github.rest.issues.listComments({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
});

return comments.data.find(comment =>
comment.user.login.endsWith('[bot]') && comment.user.type === 'Bot'
);
return comments.data.filter((comment) => {
return comment.user.login.endsWith("[bot]") &&
comment.user.type === "Bot" &&
predicate(comment);
});
};

exports.create = function (context, github, body) {
Expand All @@ -26,3 +28,9 @@ exports.delete = function (context, github, id) {
comment_id: id,
});
};

exports.deleteAll = async function (context, github, predicate = (it) => true) {
for (const item of await exports.findAll(context, github, predicate)) {
await exports.delete(context, github, item.id);
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ kotlin {
group = LifecycleBasePlugin.VERIFICATION_GROUP
description = "Runs integration tests"
testClassesDirs = output.classesDirs
outputs.upToDateWhen { false }
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ doctor {
}

configure<com.diffplug.gradle.spotless.SpotlessExtension> {
javascript {
target("**/*.js")
prettier()
}

val ktLintVersion = libs.versions.pinterest.ktlint.get()
fun FormatExtension.kotlinDefault(extension: String = "kt") {
target("**/src/**/*.$extension")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package io.ashdavies.cloud

import io.ashdavies.http.AppCheckToken
import io.ashdavies.playground.models.Event
import io.ashdavies.playground.models.FirebaseApp
import io.ktor.client.HttpClient
import io.ktor.client.HttpClientConfig
import io.ktor.client.call.body
import io.ktor.client.engine.HttpClientEngineConfig
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.request.get
import io.ktor.client.request.header
import io.ktor.client.request.post
import io.ktor.client.request.put
import io.ktor.client.request.setBody
import io.ktor.client.statement.bodyAsText
import io.ktor.http.ContentType
import io.ktor.http.HttpHeaders
import io.ktor.http.HttpStatusCode
import io.ktor.http.contentType
import io.ktor.serialization.kotlinx.json.json
import io.ktor.server.application.Application
import io.ktor.server.testing.ApplicationTestBuilder
import io.ktor.server.testing.testApplication
import io.ktor.util.KtorDsl
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.serialization.Serializable
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull

private val DefaultHttpConfig: HttpClientConfig<out HttpClientEngineConfig>.() -> Unit = {
install(ContentNegotiation, ContentNegotiation.Config::json)
}

@ExperimentalCoroutinesApi
internal class ApplicationTest {

@Test
fun `should sign in with custom token`() = testMainApplication { client ->
val apiKey = requireNotNull(System.getenv("GOOGLE_PROJECT_API_KEY"))

val authResult = client.post("/firebase/auth") {
setBody(mapOf("uid" to "[email protected]"))
contentType(ContentType.Application.Json)
header("X-API-Key", apiKey)
}.body<Map<String, String>>()

assertNotNull(authResult["idToken"])
}

@Test
fun `should get events with default limit`() = testMainApplication { client ->
val response = client.get("/events") { contentType(ContentType.Application.Json) }
val body = response.body<List<Event>>()

assertEquals(50, body.size)
}

@Test
fun `should aggregate events`() = testMainApplication { client ->
val client = createClient { install(ContentNegotiation, ContentNegotiation.Config::json) }
val response = client.post("/events:aggregate")

assertEquals(HttpStatusCode.OK, response.status)
}

@Test
fun `should create test application`() = testMainApplication { client ->
val response = client.get("/hello")

assertEquals(
expected = HttpStatusCode.OK,
actual = response.status,
)

assertEquals(
actual = response.bodyAsText(),
expected = "Hello, World!",
)
}

@Test
fun `should return app check token for request`() = testMainApplication { client ->
val tokenResponse = client.post("/firebase/token") {
setBody(FirebaseApp(System.getenv("MOBILE_SDK_APP_ID")))
// headers { append("X-API-Key", playgroundApiKey) }
contentType(ContentType.Application.Json)
}.body<TokenResponse>()

assertEquals(
actual = tokenResponse.ttlMillis,
expected = 3_600_000,
)

val verifyResponse = client.put("/firebase/token:verify") {
header(HttpHeaders.AppCheckToken, tokenResponse.token)
}.body<VerifyResponse>()

assertEquals(
expected = verifyResponse.appId,
actual = verifyResponse.subject,
)
}
}

@KtorDsl
private fun testMainApplication(
configuration: HttpClientConfig<out HttpClientEngineConfig>.() -> Unit = DefaultHttpConfig,
application: Application.() -> Unit = { main() },
block: suspend ApplicationTestBuilder.(HttpClient) -> Unit,
) = testApplication {
val client = createClient(configuration)
application(application)
block(client)
}

@Serializable
private data class TokenResponse(
val ttlMillis: Long,
val token: String,
)

@Serializable
private data class VerifyResponse(
val audience: List<String>,
val expiresAt: Long,
val subject: String,
val issuedAt: Long,
val issuer: String,
val appId: String,
)

This file was deleted.

This file was deleted.

Loading

0 comments on commit ce2c577

Please sign in to comment.