Skip to content
Closed
Show file tree
Hide file tree
Changes from 6 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
4 changes: 4 additions & 0 deletions plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ gradlePlugin {
id = "com.automattic.android.publish-to-s3-without-sources"
implementationClass = "com.automattic.android.publish.PublishToS3WithoutSourcesPlugin"
}
plugins.register("ai-docs") {
id = "com.automattic.android.ai-docs"
implementationClass = "com.automattic.android.publish.AiDocsPlugin"
}
}

java {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
package com.automattic.android.publish

import org.gradle.testkit.runner.GradleRunner
import org.gradle.testkit.runner.TaskOutcome
import java.io.File
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue

class AiDocsFunctionalTest {
@Test
fun `given aiDocs with source directory, when zipAiDocs runs, then produces zip`() {
val projectDir = File("build/functionalTest-aiDocs")
projectDir.deleteRecursively()
projectDir.mkdirs()

Comment on lines +16 to +19
val docsDir = File(projectDir, "docs/ai-reference")
docsDir.mkdirs()
File(docsDir, "index.md").writeText("# API Reference\n- [posts](posts.md)\n")
File(docsDir, "posts.md").writeText("## Posts\n- listWithEditContext(params: PostListParams)\n")

projectDir.resolve("settings.gradle").writeText("")
projectDir.resolve("build.gradle.kts").writeText("""
plugins {
id("com.automattic.android.publish-to-s3")
}

aiDocs {
from(file("docs/ai-reference"))
}
""".trimIndent())

val result = GradleRunner.create()
.forwardOutput()
.withPluginClasspath()
.withArguments("zipAiDocs")
.withProjectDir(projectDir)
.build()

assertEquals(TaskOutcome.SUCCESS, result.task(":zipAiDocs")?.outcome)

val zipFile = File(projectDir, "build/ai-docs-archive/ai-docs.zip")
assertTrue(zipFile.exists(), "Zip file should exist at ${zipFile.absolutePath}")
assertTrue(zipFile.length() > 0, "Zip file should not be empty")
}

@Test
fun `given aiDocs consumer plugin with a resolve notation, when listing tasks, then resolveAiDocs is registered`() {
val projectDir = File("build/functionalTest-aiDocs-consumer")
projectDir.deleteRecursively()
projectDir.mkdirs()

Comment on lines +52 to +55
projectDir.resolve("settings.gradle").writeText("")
projectDir.resolve("build.gradle.kts").writeText("""
plugins {
id("com.automattic.android.ai-docs")
}

aiDocs {
resolve("com.example:docs:1.0.0")
}
""".trimIndent())

val result = GradleRunner.create()
.forwardOutput()
.withPluginClasspath()
.withArguments("tasks", "--all")
.withProjectDir(projectDir)
.build()

assertEquals(TaskOutcome.SUCCESS, result.task(":tasks")?.outcome)
assertTrue(result.output.contains("resolveAiDocs"), "resolveAiDocs task should be registered")
}

@Test
fun `given both publish-to-s3 and ai-docs plugins, when configuring, then the aiDocs extension is shared`() {
val projectDir = File("build/functionalTest-aiDocs-both")
projectDir.deleteRecursively()
projectDir.mkdirs()

Comment on lines +80 to +83
projectDir.resolve("settings.gradle").writeText("")
projectDir.resolve("build.gradle.kts").writeText("""
plugins {
id("com.automattic.android.publish-to-s3")
id("com.automattic.android.ai-docs")
}

aiDocs {
resolve("com.example:docs:1.0.0")
}
""".trimIndent())

val result = GradleRunner.create()
.forwardOutput()
.withPluginClasspath()
.withArguments("tasks", "--all")
.withProjectDir(projectDir)
.build()

assertEquals(TaskOutcome.SUCCESS, result.task(":tasks")?.outcome)
assertTrue(result.output.contains("resolveAiDocs"), "resolveAiDocs task should be registered")
}

@Test
fun `given a published ai-docs artifact, when resolveAiDocs runs, then it unpacks and cleans stale versions`() {
val projectDir = File("build/functionalTest-aiDocs-resolve")
projectDir.deleteRecursively()
projectDir.mkdirs()

val repoDir = File(projectDir, "maven-repo")
publishFakeAiDocs(repoDir, version = "1.0.0", entry = "index.md", content = "# v1\n")
publishFakeAiDocs(repoDir, version = "2.0.0", entry = "index.md", content = "# v2\n")

projectDir.resolve("settings.gradle").writeText("")

fun resolve(version: String): TaskOutcome? {
projectDir.resolve("build.gradle.kts").writeText("""
plugins {
id("com.automattic.android.ai-docs")
}

repositories {
maven { url = uri("${repoDir.toURI()}") }
}

aiDocs {
resolve("com.example:docs:$version")
}
""".trimIndent())

return GradleRunner.create()
.forwardOutput()
.withPluginClasspath()
// --configuration-cache guards against carrying non-serializable values in the task.
.withArguments("resolveAiDocs", "--configuration-cache")
.withProjectDir(projectDir)
.build()
.task(":resolveAiDocs")?.outcome
}

assertEquals(TaskOutcome.SUCCESS, resolve("1.0.0"))
val v1 = File(projectDir, "build/ai-docs/com.example/docs/1.0.0/index.md")
assertTrue(v1.exists(), "v1 docs should be unpacked at ${v1.absolutePath}")

assertEquals(TaskOutcome.SUCCESS, resolve("2.0.0"))
val v2 = File(projectDir, "build/ai-docs/com.example/docs/2.0.0/index.md")
assertTrue(v2.exists(), "v2 docs should be unpacked")
assertFalse(
File(projectDir, "build/ai-docs/com.example/docs/1.0.0").exists(),
"stale v1 docs should be removed"
)
}

@Test
fun `given a dependency without an ai-docs artifact, when resolveAiDocs runs, then it succeeds with no output`() {
val projectDir = File("build/functionalTest-aiDocs-missing")
projectDir.deleteRecursively()
projectDir.mkdirs()

val repoDir = File(projectDir, "maven-repo")
// Module exists (POM) but publishes no `ai-docs` classifier artifact.
writePom(repoDir, "1.0.0")

projectDir.resolve("settings.gradle").writeText("")
projectDir.resolve("build.gradle.kts").writeText("""
plugins {
id("com.automattic.android.ai-docs")
}

repositories {
maven { url = uri("${repoDir.toURI()}") }
}

aiDocs {
resolve("com.example:docs:1.0.0")
}
""".trimIndent())

val result = GradleRunner.create()
.forwardOutput()
.withPluginClasspath()
.withArguments("resolveAiDocs")
.withProjectDir(projectDir)
.build()

assertEquals(TaskOutcome.SUCCESS, result.task(":resolveAiDocs")?.outcome)
assertFalse(
File(projectDir, "build/ai-docs/com.example").exists(),
"no docs should be unpacked when the dependency has no ai-docs artifact"
)
}

/** Lays out a POM + docs zip at the Maven coordinate `com.example:docs:<version>:ai-docs@zip`. */
private fun publishFakeAiDocs(repoDir: File, version: String, entry: String, content: String) {
val artifactDir = writePom(repoDir, version)
ZipOutputStream(File(artifactDir, "docs-$version-ai-docs.zip").outputStream()).use { zos ->
zos.putNextEntry(ZipEntry(entry))
zos.write(content.toByteArray())
zos.closeEntry()
}
}

/** Writes a minimal Maven POM for `com.example:docs:<version>` and returns its directory. */
private fun writePom(repoDir: File, version: String): File {
val artifactDir = File(repoDir, "com/example/docs/$version").apply { mkdirs() }
File(artifactDir, "docs-$version.pom").writeText(
"""
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>docs</artifactId>
<version>$version</version>
<packaging>jar</packaging>
</project>
""".trimIndent()
)
return artifactDir
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.automattic.android.publish

import org.gradle.api.file.Directory
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Provider
import java.io.File
import javax.inject.Inject

open class AiDocsExtension @Inject constructor(objects: ObjectFactory) {
val sourceDirectory: DirectoryProperty = objects.directoryProperty()

val dependencies: ListProperty<String> = objects.listProperty(String::class.java)

fun from(directory: File) {
sourceDirectory.set(directory)
}

fun from(directory: Directory) {
sourceDirectory.set(directory)
}

fun from(directory: Provider<Directory>) {
sourceDirectory.set(directory)
}

fun resolve(dependencyNotation: String) {
dependencies.add(dependencyNotation)
}
}
Loading
Loading