dbx/agents/scripts/validate_agents_test.py

438 lines
17 KiB
Python

import json
import tempfile
import textwrap
import unittest
import zipfile
from pathlib import Path
import validate_agents
import validate_agent_jars
class ValidateAgentsTest(unittest.TestCase):
def test_versions_must_match_included_agent_modules(self):
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
(root / "settings.gradle").write_text(
"include 'common', 'test-support', 'h2', 'oracle'\n",
encoding="utf-8",
)
(root / "versions.json").write_text(
json.dumps({"h2": "0.1.0", "mongodb": "0.1.0"}),
encoding="utf-8",
)
problems = validate_agents.validate_versions(root)
self.assertEqual(
[
"included module missing version: oracle",
"versions key not included: mongodb",
],
problems,
)
def test_versions_support_driver_modules_variable(self):
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
(root / "settings.gradle").write_text(
"def infrastructureModules = ['common', 'test-support']\n"
"def driverModules = ['h2', 'oracle']\n"
"include(*(infrastructureModules + driverModules))\n",
encoding="utf-8",
)
(root / "versions.json").write_text(
json.dumps({"h2": "0.1.0", "oracle": "0.1.0"}),
encoding="utf-8",
)
self.assertEqual([], validate_agents.validate_versions(root))
def test_source_scan_rejects_old_execute_query_patterns(self):
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
source = root / "h2/src/main/java/com/dbx/agent/h2/H2Agent.java"
source.parent.mkdir(parents=True)
source.write_text(
textwrap.dedent(
"""
private val QUERY_PREFIXES = listOf("SELECT")
fun executeQuery(sql: String) {
val trimmedSql = sql.trim()
stmt.executeUpdate(trimmedSql)
}
"""
),
encoding="utf-8",
)
problems = validate_agents.validate_source_patterns(root)
self.assertEqual(
[
"h2/src/main/java/com/dbx/agent/h2/H2Agent.java:2: forbidden local SQL prefix classifier",
"h2/src/main/java/com/dbx/agent/h2/H2Agent.java:5: forbidden executeUpdate(trimmedSql) in query execution",
],
problems,
)
def test_jdbc_architecture_requires_shared_base_for_new_agents(self):
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
module = root / "example"
source = module / "src/main/java/com/dbx/agent/example/ExampleAgent.java"
source.parent.mkdir(parents=True)
(module / "build.gradle").write_text(
textwrap.dedent(
"""
tasks.named('shadowJar') {
manifest {
attributes(
'Agent-Label': 'Example',
'Main-Class': 'com.dbx.agent.example.ExampleAgent'
)
}
}
"""
),
encoding="utf-8",
)
source.write_text(
textwrap.dedent(
"""
package com.dbx.agent.example;
import com.dbx.agent.BaseDatabaseAgent;
import java.sql.DriverManager;
public final class ExampleAgent extends BaseDatabaseAgent {
public void connect() throws Exception {
Class.forName("example.Driver");
DriverManager.getConnection("jdbc:example");
}
}
"""
),
encoding="utf-8",
)
problems = validate_agents.validate_jdbc_architecture(root, {"example"})
self.assertEqual(
[
"example/src/main/java/com/dbx/agent/example/ExampleAgent.java: JDBC agents must extend AbstractJdbcAgent, ConfiguredJdbcAgent, or PostgresLikeAgent",
"example/src/main/java/com/dbx/agent/example/ExampleAgent.java:9: copied driver loading; use shared JDBC foundation",
"example/src/main/java/com/dbx/agent/example/ExampleAgent.java:10: copied JDBC connection creation; use shared JDBC foundation",
],
problems,
)
def test_jdbc_architecture_allows_documented_migration_exceptions(self):
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
module = root / "oracle"
source = module / "src/main/java/com/dbx/agent/oracle/OracleAgent.java"
source.parent.mkdir(parents=True)
(module / "build.gradle").write_text(
textwrap.dedent(
"""
tasks.named('shadowJar') {
manifest {
attributes(
'Agent-Label': 'Oracle',
'Main-Class': 'com.dbx.agent.oracle.OracleAgent'
)
}
}
"""
),
encoding="utf-8",
)
source.write_text(
"package com.dbx.agent.oracle; public final class OracleAgent extends BaseDatabaseAgent {}\n",
encoding="utf-8",
)
self.assertEqual([], validate_agents.validate_jdbc_architecture(root, {"oracle"}))
def test_authoring_template_must_use_shared_foundation(self):
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
template = root / "docs/examples/jdbc-agent-template/src/main/java/com/dbx/agent/template/TemplateAgent.java"
template.parent.mkdir(parents=True)
template.write_text(
textwrap.dedent(
"""
package com.dbx.agent.template;
import com.dbx.agent.BaseDatabaseAgent;
import java.sql.DriverManager;
public final class TemplateAgent extends BaseDatabaseAgent {
void connect() throws Exception {
DriverManager.getConnection("jdbc:template");
}
}
"""
),
encoding="utf-8",
)
self.assertEqual(
[
"docs/examples/jdbc-agent-template/src/main/java/com/dbx/agent/template/TemplateAgent.java: template must use shared JDBC foundation",
"docs/examples/jdbc-agent-template/src/main/java/com/dbx/agent/template/TemplateAgent.java: template contains copied JDBC connection creation",
],
validate_agents.validate_authoring_template(root),
)
def test_manifest_validation_requires_registry_fields(self):
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
module = root / "h2"
module.mkdir()
(root / "build.gradle").write_text(
textwrap.dedent(
"""
configure(agentProjects) {
tasks.named('shadowJar') {
archiveBaseName = "dbx-agent-${project.name}"
}
}
"""
),
encoding="utf-8",
)
(module / "build.gradle").write_text(
textwrap.dedent(
"""
tasks.named('shadowJar') {
manifest {
attributes(
'Agent-Label': 'H2'
)
}
}
"""
),
encoding="utf-8",
)
problems = validate_agents.validate_manifest_fields(root, {"h2"})
self.assertEqual(
["h2/build.gradle: missing Main-Class manifest attribute"],
problems,
)
def test_manifest_validation_requires_main_class_source(self):
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
module = root / "h2"
module.mkdir()
(root / "build.gradle").write_text(
'archiveBaseName = "dbx-agent-${project.name}"\n',
encoding="utf-8",
)
(module / "build.gradle").write_text(
textwrap.dedent(
"""
tasks.named('shadowJar') {
manifest {
attributes(
'Agent-Label': 'H2',
'Main-Class': 'com.dbx.agent.h2.H2Agent'
)
}
}
"""
),
encoding="utf-8",
)
problems = validate_agents.validate_manifest_fields(root, {"h2"})
self.assertEqual(
["h2/build.gradle: Main-Class source not found: h2/src/main/java/com/dbx/agent/h2/H2Agent.java"],
problems,
)
source = module / "src/main/java/com/dbx/agent/h2/H2Agent.java"
source.parent.mkdir(parents=True)
source.write_text("package com.dbx.agent.h2; public final class H2Agent {}", encoding="utf-8")
self.assertEqual([], validate_agents.validate_manifest_fields(root, {"h2"}))
def test_manifest_validation_supports_drivers_directory(self):
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
module = root / "drivers/h2"
source = module / "src/main/java/com/dbx/agent/h2/H2Agent.java"
source.parent.mkdir(parents=True)
source.write_text("package com.dbx.agent.h2; public final class H2Agent {}", encoding="utf-8")
(root / "build.gradle").write_text(
'archiveBaseName = "dbx-agent-${project.name}"\n',
encoding="utf-8",
)
(module / "build.gradle").write_text(
textwrap.dedent(
"""
tasks.named('shadowJar') {
manifest {
attributes(
'Agent-Label': 'H2',
'Main-Class': 'com.dbx.agent.h2.H2Agent'
)
}
}
"""
),
encoding="utf-8",
)
self.assertEqual([], validate_agents.validate_manifest_fields(root, {"h2"}))
def test_kotlin_residue_scan_rejects_kt_and_kts_files_outside_build_dirs(self):
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
forbidden_source = root / "h2/src/main/kotlin/com/dbx/agent/h2/H2Agent.kt"
forbidden_build = root / "settings.gradle.kts"
ignored_build_output = root / "h2/build/tmp/Generated.kt"
ignored_gradle_cache = root / ".gradle/caches/init.gradle.kts"
for path in (
forbidden_source,
forbidden_build,
ignored_build_output,
ignored_gradle_cache,
):
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text("", encoding="utf-8")
problems = validate_agents.validate_no_kotlin_residue(root)
self.assertEqual(
[
"h2/src/main/kotlin/com/dbx/agent/h2/H2Agent.kt: forbidden Kotlin file",
"settings.gradle.kts: forbidden Kotlin file",
],
problems,
)
def test_release_runtime_keys_match_java_21_default_and_oracle_8_exception(self):
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
workflow = root / ".github/workflows/release.yml"
workflow.parent.mkdir(parents=True)
workflow.write_text(
textwrap.dedent(
'''
strategy:
matrix:
include:
- jre-key: "21"
java-version: "21"
detect_jre_key() {
case "$name" in
oracle-10g) echo "8" ;;
*) echo "21" ;;
esac
}
cat > release/agent-registry.json <<EOF
{
"jres": {
"21": {
"version": "21.0.11"
}
}
}
EOF
legacy_jar_url="https://github.com/${REPO}/releases/download/${TAG}/dbx-agent-${name}-legacy-placeholder.jar"
'''
),
encoding="utf-8",
)
self.assertEqual([], validate_agents.validate_release_runtime_keys(root))
workflow.write_text(
textwrap.dedent(
'''
strategy:
matrix:
include:
- jre-key: "17"
java-version: "21"
detect_jre_key() {
case "$name" in
oracle-10g) echo "8" ;;
*) echo "17" ;;
esac
}
cat > release/agent-registry.json <<EOF
{
"jres": {
"17": {
"version": "21.0.11"
}
}
}
EOF
'''
),
encoding="utf-8",
)
self.assertEqual(
[
"release workflow must build the default JRE with key 21",
"non-legacy agents must use JRE key 21",
"registry must publish Java 21 under JRE key 21",
"native-only registry entries must publish a legacy jar placeholder for older DBX clients",
"release workflow must not build Java 21 under JRE key 17",
"non-legacy agents must not use JRE key 17",
"registry must not publish Java 21 under JRE key 17",
],
validate_agents.validate_release_runtime_keys(root),
)
def test_agent_jar_validation_requires_main_class_entry(self):
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
jar = root / "h2/build/libs/dbx-agent-h2.jar"
jar.parent.mkdir(parents=True)
with zipfile.ZipFile(jar, "w") as archive:
archive.writestr(
"META-INF/MANIFEST.MF",
"Manifest-Version: 1.0\n"
"Agent-Label: H2\n"
"Main-Class: com.dbx.agent.h2.H2Agent\n\n",
)
self.assertEqual(
["h2/build/libs/dbx-agent-h2.jar: Main-Class class not found: com/dbx/agent/h2/H2Agent.class"],
validate_agent_jars.validate_agent_jars(root),
)
with zipfile.ZipFile(jar, "a") as archive:
archive.writestr("com/dbx/agent/h2/H2Agent.class", b"class-bytes")
self.assertEqual([], validate_agent_jars.validate_agent_jars(root))
def test_agent_jar_validation_supports_drivers_directory(self):
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
jar = root / "drivers/h2/build/libs/dbx-agent-h2.jar"
jar.parent.mkdir(parents=True)
with zipfile.ZipFile(jar, "w") as archive:
archive.writestr(
"META-INF/MANIFEST.MF",
"Manifest-Version: 1.0\n"
"Agent-Label: H2\n"
"Main-Class: com.dbx.agent.h2.H2Agent\n\n",
)
archive.writestr("com/dbx/agent/h2/H2Agent.class", b"class-bytes")
self.assertEqual([], validate_agent_jars.validate_agent_jars(root))
if __name__ == "__main__":
unittest.main()