Skip to content

Conversation

@u-ways
Copy link
Owner

@u-ways u-ways commented Nov 6, 2025

Background

This plugin is a fantastic candidate to reduce our settings convention boilerplate for enterprise projects hosted on the GH platform. As such, I have invested some of my free time to improve this project's configurations and implementation details.

I have introduced full backwards compatibility with projects that rely on Gradle properties instead of environment variables, making it an attractive drop-in replacement in established projects for users who want the GH CLI authentication option.

On top of that, I have made it possible to functionally test this plugin with the GH CLI processor by creating a suitable fake. This pushed me to complete the test coverage for acceptance requirements, which adds confidence for buy-ins and adoption opportunities.

Finally, I have fully revised the documentation to ensure its accuracy and alignment with the latest details.

Implementation Details

There are a lot of technical details to cover. I will add some highlights here, and I am more than happy to call for a complete walkthrough and reasoning breakdown.

⚙️ GitHub CLI Auth Processor and Parser

The implementation details for the GH auth highly resembled processor and parser patterns. I have refactored them to be clear of that, gave them better names with complete test coverage. I have also settled for Gh as a suffix rather than GitHub for naming, as I noticed there was mixed usage of the two.

⚙️ Gh CLI Auth Base (Abstract Class) to Reduce Duplication!

The GH CLI Auth project and settings plugin share a lot of commonality, so it was natural to create a base class that holds those details for ease of maintenance and understanding of the different functionality between the two. I also added a nice little cache implementation for the GH org and credentials!

🧪 Functional Tests Cross-Testing Coverage

Since both the project and settings plugin share the same base abstract class, I have completed the coverage by crossing which base common features to test between the two. For example, some CLI auth logic might have been covered in the project plugin functional tests, whereas the property logic might've been covered in the settings plugin functional tests.

🧪 How GH CLI Processor Testing Has Been Made Possible?

The secret is this little fake I have written:

package io.github.adelinosousa.gradle.plugins.support

import java.io.File

/**
 * Fake GitHub CLI authentication script for functional tests.
 *
 * This class creates a fake `gh` CLI script that simulates authentication
 * by outputting a predefined token and scopes.
 */
class GhCliAuthFake(
    private val projectDir: File
) {
    companion object {
        internal const val DEFAULT_USER_VALUE: String = "testuser"
        internal const val DEFAULT_TOKEN_VALUE: String = "ghp_mocked_token_1234567890"
        internal val DEFAULT_VALID_SCOPES: List<String> = listOf("read:packages", "read:org", "repo")
    }

    internal lateinit var fakeGhScript: File
        private set

    internal fun execute(
        token: String = DEFAULT_TOKEN_VALUE,
        validScopes: List<String> = DEFAULT_VALID_SCOPES,
        username: String = DEFAULT_USER_VALUE
    ) {
        fakeGhScript = projectDir
            .resolve("bin")
            .apply { mkdirs() }
            .resolve("gh")
            .apply {
                writeText(
                    """
                    #!/bin/bash
                    echo "Logged in to github.com account $username (keyring)"
                    echo "Token: $token"
                    echo "Token scopes: ${validScopes.joinToString(", ")}"
                    exit 0
                    """
                )
            }

        ProcessBuilder("chmod", "+x", fakeGhScript.absolutePath)
            .start()
            .waitFor()
    }
}

The GH CLI's behavior is NOT our responsibility to test (it's not software the project has written); as such, it's acceptable to fake its response, much like stubbing an API.

🐛 Kotlin JVM and Kotlin DSL Were Not Aligned

The project was applying Kotlin JVM version 2.1.20, yet the embedded Kotlin is 2.0.21. This means the following:

plugins {
    `kotlin-dsl`
    alias(libs.plugins.kotlin.jvm)
}

was applying to different Kotlin APIs! This can lead to unexpected conflicts, so I have set the KT version to match what the current Gradle wrapper version embeds.

🐛 Functional Test Setup Configuration Was Incorrect

The functionalTest src was being picked up as non-test sources. Using the JvmTestSuite, I have correctly configured and set up this source to correctly identify the files there as tests (you will see them marked as "green" instead of "blue" from now on in the IDE).

@Suppress("UnstableApiUsage")
testing.suites.register<JvmTestSuite>("functionalTest").configure {
    dependencies { implementation(gradleTestKit()) }
    targets { all { testTask.configure { useJUnitPlatform() } } }
    configurations["functionalTestImplementation"].extendsFrom(configurations["testImplementation"])
    configurations["functionalTestRuntimeOnly"].extendsFrom(configurations["testRuntimeOnly"])
    tasks.named<Task>("check") { dependsOn(this@configure) }
    kotlin.target.compilations { named { it == this@configure.name }.configureEach { associateWith(getByName("main")) } }
}

⚠️ Warnings

A bunch of warnings were sorted out based on the current Gradle version being used. See:

/**
 * Enable strict explicit API mode for this project as this is a public plugin.
 */
kotlin.explicitApi = org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode.Strict

tasks.named<Test>("test") {
    /**
     * Byte-buddy-agent is a dependency of mockk, and it is not a serviceability tool.
     * So, we need to enable dynamic agent loading to hide the warning and make it work in future JDK versions.
     */
    jvmArgs("-XX:+EnableDynamicAgentLoading")
    /**
     * > OpenJDK 64-Bit Server VM warning:
     * > Sharing is only supported for bootloader classes because the bootstrap classpath has been appended.
     * For further details, see: https://github.com/gradle/gradle/issues/19989 is resolved.
     */
    jvmArgs("-Xshare:off")
    useJUnitPlatform()
}

If you need any more details, please let me know!


…ble or the properties file, a static default can be forgotten and can lead to accidentally setting the wrong version, instead enforce the availability of the gradle.publish.version property by default
… as it enable users to build on top of the plugin if desired
…t appender can be used for this log, plus set the log level to error
…improve single responsibility, and encapsulation
…ove error handling, and improve dynamic GH binary collector logic
…ganization for clarity and avoid naming clash
….kts to remove IDE marked errors, and remove empty block for functional test sources
…edded version, and correctly set functional tests as test sources rather than main
…ys have an option to override, improve logging, and encapsulate GhCliAuthProcessor provider creation
…duplication, and notable improve acceptance test coverage at the integration level!
@u-ways u-ways force-pushed the u-contributions branch 2 times, most recently from 44055b1 to f7f6574 Compare November 6, 2025 21:06
@u-ways u-ways changed the title 2.0.0 - Greatly Improve Test Coverage, Project Structure, and fix some Configuration Issues 2.0.0 Nov 6, 2025
@u-ways u-ways merged commit f6df45e into main Nov 6, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants