mirror of
https://github.com/sudosylabs/vnidrop.git
synced 2026-08-05 02:29:55 +02:00
feat(config): shared app.properties for app-wide constants
Add a single source of truth (root app.properties) for public app-wide constants, injected at build time on both platforms instead of hardcoding. - Apple: generate-appconfig.sh -> Generated/AppConfig.swift (wired into `make apple-app-config`), consumed as AppConfig.privacyPolicyURL. - KMP: generateAppConfig task -> AppConfig.kt (mirrors DiagnosticsBuildConfig), consumed as AppConfig.PRIVACY_POLICY_URL. Replaces the stale hardcoded privacy-policy URL on both sides with https://vnidrop.sudosy.fr/privacy/. Also fix the Apple release core build: disable release LTO in build-core.sh (Cargo forbids lto in a build-override) to avoid the proc-macro "mis-aligned LINKEDIT string pool" corruption, so release archives are compact instead of shipping the debug core. Update the app icon. Tests: shell test for the generator (escaping, missing/duplicate key), plus XCTest and jvmTest asserting the generated value matches app.properties.
This commit is contained in:
10
Makefile
10
Makefile
@@ -12,7 +12,7 @@ include $(ROOT)/make/release.mk
|
|||||||
.PHONY: format test check check-rust audit-rust test-rust test-rust-all
|
.PHONY: format test check check-rust audit-rust test-rust test-rust-all
|
||||||
.PHONY: test-rust-transfer test-rust-approval test-rust-lifecycle test-rust-output-sink
|
.PHONY: test-rust-transfer test-rust-approval test-rust-lifecycle test-rust-output-sink
|
||||||
.PHONY: check-shared test-shared test-android-host check-android verify-android-libs build-android run-desktop
|
.PHONY: check-shared test-shared test-android-host check-android verify-android-libs build-android run-desktop
|
||||||
.PHONY: apple-core apple-version-config apple-project open-apple-project open-apple build-apple-macos build-apple-ios check-apple package-apple-core
|
.PHONY: apple-core apple-version-config apple-app-config apple-project open-apple-project open-apple build-apple-macos build-apple-ios check-apple package-apple-core
|
||||||
.PHONY: prepare-release check-version check-release check-localization localization localization-migrate
|
.PHONY: prepare-release check-version check-release check-localization localization localization-migrate
|
||||||
.PHONY: check-docs run-docs check-diagnostics run-diagnostics diagnostics-db-local diagnostics-db-remote diagnostics-typegen deploy-diagnostics
|
.PHONY: check-docs run-docs check-diagnostics run-diagnostics diagnostics-db-local diagnostics-db-remote diagnostics-typegen deploy-diagnostics
|
||||||
|
|
||||||
@@ -71,8 +71,9 @@ check-version: ## Validate the canonical version and its platform mappings.
|
|||||||
cd $(ROOT) && $(GRADLE) verifyVersion $(GRADLE_FLAGS)
|
cd $(ROOT) && $(GRADLE) verifyVersion $(GRADLE_FLAGS)
|
||||||
|
|
||||||
check-release: ## Validate coordinated release scripts and workflow YAML.
|
check-release: ## Validate coordinated release scripts and workflow YAML.
|
||||||
cd $(ROOT) && bash -n apple/scripts/notarize.sh apple/scripts/sign-exported-app.sh apple/scripts/tests/test-notarize.sh apple/scripts/tests/test-sign-exported-app.sh packaging/android/build-release.sh packaging/android/verify-apk-signature.sh packaging/android/tests/test_verify_apk_signature.sh packaging/release/assemble-release.sh packaging/release/test-assemble-release.sh packaging/release/test-release-config.sh
|
cd $(ROOT) && bash -n apple/scripts/notarize.sh apple/scripts/sign-exported-app.sh apple/scripts/tests/test-notarize.sh apple/scripts/tests/test-sign-exported-app.sh apple/scripts/generate-appconfig.sh apple/scripts/tests/test-generate-appconfig.sh packaging/android/build-release.sh packaging/android/verify-apk-signature.sh packaging/android/tests/test_verify_apk_signature.sh packaging/release/assemble-release.sh packaging/release/test-assemble-release.sh packaging/release/test-release-config.sh
|
||||||
cd $(ROOT) && apple/scripts/tests/test-notarize.sh
|
cd $(ROOT) && apple/scripts/tests/test-notarize.sh
|
||||||
|
cd $(ROOT) && apple/scripts/tests/test-generate-appconfig.sh
|
||||||
cd $(ROOT) && apple/scripts/tests/test-sign-exported-app.sh
|
cd $(ROOT) && apple/scripts/tests/test-sign-exported-app.sh
|
||||||
cd $(ROOT) && packaging/android/tests/test_verify_apk_signature.sh
|
cd $(ROOT) && packaging/android/tests/test_verify_apk_signature.sh
|
||||||
cd $(ROOT) && packaging/release/test-assemble-release.sh
|
cd $(ROOT) && packaging/release/test-assemble-release.sh
|
||||||
@@ -135,7 +136,10 @@ apple-core: ## Build the Rust XCFramework and generated Swift bindings.
|
|||||||
apple-version-config: ## Generate derived Store and Direct Apple build settings.
|
apple-version-config: ## Generate derived Store and Direct Apple build settings.
|
||||||
cd $(ROOT) && packaging/version/generate-apple-xcconfig.sh all
|
cd $(ROOT) && packaging/version/generate-apple-xcconfig.sh all
|
||||||
|
|
||||||
apple-project: apple-core localization apple-version-config ## Generate the native Apple Xcode project.
|
apple-app-config: ## Generate AppConfig.swift from the shared app.properties.
|
||||||
|
cd $(ROOT) && apple/scripts/generate-appconfig.sh
|
||||||
|
|
||||||
|
apple-project: apple-core localization apple-version-config apple-app-config ## Generate the native Apple Xcode project.
|
||||||
cd $(ROOT)/apple && $(XCODEGEN) generate
|
cd $(ROOT)/apple && $(XCODEGEN) generate
|
||||||
|
|
||||||
open-apple-project: apple-project ## Generate and open the native Apple Xcode project.
|
open-apple-project: apple-project ## Generate and open the native Apple Xcode project.
|
||||||
|
|||||||
4
app.properties
Normal file
4
app.properties
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# Public, app-wide configuration shared by every platform (Apple + KMP).
|
||||||
|
# Plain KEY=VALUE so it is parsed identically by shell, Gradle, and codegen.
|
||||||
|
# Injected into the apps at build time — never hardcode these values in app code.
|
||||||
|
PRIVACY_POLICY_URL=https://vnidrop.sudosy.fr/privacy/
|
||||||
39
apple/Tests/AppConfigTests.swift
Normal file
39
apple/Tests/AppConfigTests.swift
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import XCTest
|
||||||
|
@testable import VniDrop
|
||||||
|
|
||||||
|
/// Verifies the build-time `AppConfig` (generated from the shared `app.properties`)
|
||||||
|
/// exposes the expected, well-formed values to the app.
|
||||||
|
final class AppConfigTests: XCTestCase {
|
||||||
|
func testPrivacyPolicyURLIsTheExpectedHTTPSEndpoint() {
|
||||||
|
let url = AppConfig.privacyPolicyURL
|
||||||
|
XCTAssertEqual(url.scheme, "https", "Privacy policy URL must be https")
|
||||||
|
XCTAssertEqual(url.absoluteString, "https://vnidrop.sudosy.fr/privacy/")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testPrivacyPolicyURLMatchesTheSharedConfigFile() throws {
|
||||||
|
// Cross-check the generated constant against the single source of truth so a
|
||||||
|
// broken generator (or drift) is caught, not just a hardcoded copy.
|
||||||
|
let expected = try Self.privacyURLFromAppProperties()
|
||||||
|
XCTAssertEqual(AppConfig.privacyPolicyURL.absoluteString, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads `PRIVACY_POLICY_URL` from the repo's `app.properties` by walking up
|
||||||
|
/// from this source file's location to the repository root.
|
||||||
|
private static func privacyURLFromAppProperties() throws -> String {
|
||||||
|
var dir = URL(fileURLWithPath: #filePath).deletingLastPathComponent()
|
||||||
|
for _ in 0..<8 {
|
||||||
|
let candidate = dir.appendingPathComponent("app.properties")
|
||||||
|
if FileManager.default.fileExists(atPath: candidate.path) {
|
||||||
|
let contents = try String(contentsOf: candidate, encoding: .utf8)
|
||||||
|
for line in contents.split(whereSeparator: \.isNewline) {
|
||||||
|
if line.hasPrefix("PRIVACY_POLICY_URL=") {
|
||||||
|
return String(line.dropFirst("PRIVACY_POLICY_URL=".count))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw XCTSkip("PRIVACY_POLICY_URL missing in \(candidate.path)")
|
||||||
|
}
|
||||||
|
dir.deleteLastPathComponent()
|
||||||
|
}
|
||||||
|
throw XCTSkip("app.properties not found from \(#filePath)")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -375,7 +375,7 @@ struct StorageSettings: View {
|
|||||||
struct AboutSettings: View {
|
struct AboutSettings: View {
|
||||||
@ObservedObject var model: SettingsModel
|
@ObservedObject var model: SettingsModel
|
||||||
|
|
||||||
private static let privacyPolicyURL = URL(string: "https://github.com/vnidrop/vnidrop")!
|
private static let privacyPolicyURL = AppConfig.privacyPolicyURL
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Section {
|
Section {
|
||||||
|
|||||||
@@ -1,62 +1,57 @@
|
|||||||
{
|
{
|
||||||
"fill": {
|
"fill" : {
|
||||||
"linear-gradient": [
|
"linear-gradient" : [
|
||||||
"extended-gray:1.00000,1.00000",
|
"extended-gray:1.00000,1.00000",
|
||||||
"display-p3:0.55433,0.59923,0.92884,1.00000"
|
"srgb:0.84942,0.81480,0.95401,1.00000"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"groups": [
|
"groups" : [
|
||||||
{
|
{
|
||||||
"blend-mode": "normal",
|
"blend-mode" : "normal",
|
||||||
"blur-material": null,
|
"blur-material" : null,
|
||||||
"layers": [
|
"layers" : [
|
||||||
{
|
{
|
||||||
"image-name": "Mask.svg",
|
"image-name" : "Mask.svg",
|
||||||
"name": "Mask"
|
"name" : "Mask"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"lighting": "individual",
|
"lighting" : "individual",
|
||||||
"refractivity": {
|
"shadow" : {
|
||||||
"depth": 0.5,
|
"kind" : "neutral",
|
||||||
"enabled": true,
|
"opacity" : 0.6
|
||||||
"strength": 0
|
},
|
||||||
},
|
"specular" : true,
|
||||||
"shadow": {
|
"translucency" : {
|
||||||
"kind": "neutral",
|
"enabled" : true,
|
||||||
"opacity": 0.6
|
"value" : 0.8
|
||||||
},
|
}
|
||||||
"specular": true,
|
},
|
||||||
"translucency": {
|
{
|
||||||
"enabled": true,
|
"layers" : [
|
||||||
"value": 0.8
|
{
|
||||||
}
|
"image-name" : "Drop.svg",
|
||||||
},
|
"name" : "Drop"
|
||||||
{
|
},
|
||||||
"layers": [
|
{
|
||||||
{
|
"image-name" : "U.svg",
|
||||||
"image-name": "Drop.svg",
|
"name" : "U"
|
||||||
"name": "Drop"
|
}
|
||||||
},
|
],
|
||||||
{
|
"lighting" : "combined",
|
||||||
"image-name": "U.svg",
|
"shadow" : {
|
||||||
"name": "U"
|
"kind" : "layer-color",
|
||||||
}
|
"opacity" : 0.8
|
||||||
],
|
},
|
||||||
"lighting": "combined",
|
"translucency" : {
|
||||||
"shadow": {
|
"enabled" : true,
|
||||||
"kind": "neutral",
|
"value" : 0.4
|
||||||
"opacity": 0.6
|
}
|
||||||
},
|
}
|
||||||
"translucency": {
|
],
|
||||||
"enabled": true,
|
"supported-platforms" : {
|
||||||
"value": 0.4
|
"circles" : [
|
||||||
}
|
"watchOS"
|
||||||
}
|
],
|
||||||
],
|
"squares" : "shared"
|
||||||
"supported-platforms": {
|
}
|
||||||
"circles": [
|
|
||||||
"watchOS"
|
|
||||||
],
|
|
||||||
"squares": "shared"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -44,6 +44,13 @@ export MACOSX_DEPLOYMENT_TARGET="${MACOSX_DEPLOYMENT_TARGET:-15.0}"
|
|||||||
# This never touches the Rust crate — it only changes how the build is invoked.
|
# This never touches the Rust crate — it only changes how the build is invoked.
|
||||||
export CARGO_PROFILE_DEV_STRIP=none
|
export CARGO_PROFILE_DEV_STRIP=none
|
||||||
|
|
||||||
|
# The workspace `[profile.release] lto = "thin"` corrupts host proc-macro / build
|
||||||
|
# script dylibs when cross-compiling ("mis-aligned LINKEDIT string pool"). Cargo
|
||||||
|
# forbids overriding `lto` per build-override, so disable thin LTO for the whole
|
||||||
|
# release build here — the crate is still fully optimized (opt-level 3, debuginfo
|
||||||
|
# stripped), which is what shrinks the static lib. This never edits the Cargo crate.
|
||||||
|
export CARGO_PROFILE_RELEASE_LTO=false
|
||||||
|
|
||||||
IOS_TARGET="aarch64-apple-ios"
|
IOS_TARGET="aarch64-apple-ios"
|
||||||
SIM_ARM_TARGET="aarch64-apple-ios-sim"
|
SIM_ARM_TARGET="aarch64-apple-ios-sim"
|
||||||
SIM_X64_TARGET="x86_64-apple-ios"
|
SIM_X64_TARGET="x86_64-apple-ios"
|
||||||
|
|||||||
44
apple/scripts/generate-appconfig.sh
Executable file
44
apple/scripts/generate-appconfig.sh
Executable file
@@ -0,0 +1,44 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Generates apple/VniDrop/Generated/AppConfig.swift from the shared app.properties
|
||||||
|
# so app-wide constants (privacy policy URL, …) have a single source of truth
|
||||||
|
# across Apple and KMP. Regenerate instead of editing the output.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
repo_root="$(cd "$script_dir/../.." && pwd)"
|
||||||
|
config_file="${VNIDROP_APP_PROPERTIES:-$repo_root/app.properties}"
|
||||||
|
output_dir="${VNIDROP_APPLE_GENERATED_DIR:-$repo_root/apple/VniDrop/Generated}"
|
||||||
|
|
||||||
|
read_property() {
|
||||||
|
local key=$1
|
||||||
|
local value
|
||||||
|
value="$(sed -n "s/^${key}=//p" "$config_file")"
|
||||||
|
[[ -n "$value" ]] || { printf 'Missing %s in %s\n' "$key" "$config_file" >&2; exit 1; }
|
||||||
|
[[ $(printf '%s\n' "$value" | wc -l | tr -d ' ') == 1 ]] ||
|
||||||
|
{ printf 'Duplicate %s in %s\n' "$key" "$config_file" >&2; exit 1; }
|
||||||
|
printf '%s' "$value"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Escape for a Swift string literal.
|
||||||
|
swift_escape() {
|
||||||
|
printf '%s' "$1" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g'
|
||||||
|
}
|
||||||
|
|
||||||
|
privacy_url="$(read_property PRIVACY_POLICY_URL)"
|
||||||
|
|
||||||
|
mkdir -p "$output_dir"
|
||||||
|
tmp="$(mktemp "$output_dir/.AppConfig.swift.XXXXXX")"
|
||||||
|
cat > "$tmp" <<EOF
|
||||||
|
// Generated by apple/scripts/generate-appconfig.sh from app.properties.
|
||||||
|
// Regenerate this file instead of editing it.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// App-wide constants injected at build time from the shared \`app.properties\`.
|
||||||
|
enum AppConfig {
|
||||||
|
static let privacyPolicyURL = URL(string: "$(swift_escape "$privacy_url")")!
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
mv "$tmp" "$output_dir/AppConfig.swift"
|
||||||
59
apple/scripts/tests/test-generate-appconfig.sh
Executable file
59
apple/scripts/tests/test-generate-appconfig.sh
Executable file
@@ -0,0 +1,59 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Tests apple/scripts/generate-appconfig.sh: the shared app.properties is read
|
||||||
|
# correctly, values are emitted as valid escaped Swift, and a missing key fails.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
generator="$script_dir/../generate-appconfig.sh"
|
||||||
|
repo_root="$(cd "$script_dir/../../.." && pwd)"
|
||||||
|
scratch="$(mktemp -d)"
|
||||||
|
trap 'rm -rf "$scratch"' EXIT
|
||||||
|
|
||||||
|
# Run the generator against a fixture app.properties, emitting into a temp dir.
|
||||||
|
generate() {
|
||||||
|
VNIDROP_APP_PROPERTIES="$scratch/app.properties" \
|
||||||
|
VNIDROP_APPLE_GENERATED_DIR="$scratch/out" \
|
||||||
|
"$generator"
|
||||||
|
}
|
||||||
|
|
||||||
|
expect_failure() {
|
||||||
|
if "$@" >/dev/null 2>&1; then
|
||||||
|
printf 'Expected command to fail: %s\n' "$*" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_contains() {
|
||||||
|
local file=$1 needle=$2
|
||||||
|
grep -qF "$needle" "$file" ||
|
||||||
|
{ printf 'Expected %s to contain: %s\n' "$file" "$needle" >&2; exit 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
out="$scratch/out/AppConfig.swift"
|
||||||
|
|
||||||
|
# 1. Nominal value is emitted verbatim as a Swift URL literal.
|
||||||
|
printf 'PRIVACY_POLICY_URL=%s\n' 'https://example.test/privacy/' > "$scratch/app.properties"
|
||||||
|
generate
|
||||||
|
assert_contains "$out" 'URL(string: "https://example.test/privacy/")!'
|
||||||
|
assert_contains "$out" 'enum AppConfig'
|
||||||
|
|
||||||
|
# 2. Characters special to a Swift string literal are escaped.
|
||||||
|
printf 'PRIVACY_POLICY_URL=%s\n' 'https://a.test/"q"\z' > "$scratch/app.properties"
|
||||||
|
generate
|
||||||
|
assert_contains "$out" 'URL(string: "https://a.test/\"q\"\\z")!'
|
||||||
|
|
||||||
|
# 3. A missing key fails instead of emitting an empty value.
|
||||||
|
printf 'OTHER_KEY=value\n' > "$scratch/app.properties"
|
||||||
|
expect_failure generate
|
||||||
|
|
||||||
|
# 4. A duplicated key fails.
|
||||||
|
printf 'PRIVACY_POLICY_URL=a\nPRIVACY_POLICY_URL=b\n' > "$scratch/app.properties"
|
||||||
|
expect_failure generate
|
||||||
|
|
||||||
|
# 5. The real committed app.properties produces an https URL.
|
||||||
|
VNIDROP_APPLE_GENERATED_DIR="$scratch/real" "$generator"
|
||||||
|
assert_contains "$scratch/real/AppConfig.swift" 'URL(string: "https://'
|
||||||
|
|
||||||
|
printf 'generate-appconfig tests passed.\n'
|
||||||
@@ -110,6 +110,55 @@ val generateDiagnosticsBuildConfig by tasks.registering {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// App-wide public constants (privacy policy URL, …) from the shared app.properties,
|
||||||
|
// so Apple and KMP read one source of truth instead of hardcoding values.
|
||||||
|
val appProperties = java.util.Properties().apply {
|
||||||
|
rootProject.file("app.properties").inputStream().use(::load)
|
||||||
|
}
|
||||||
|
val privacyPolicyUrl: String = appProperties.getProperty("PRIVACY_POLICY_URL")?.trim().orEmpty()
|
||||||
|
check(privacyPolicyUrl.isNotEmpty()) { "PRIVACY_POLICY_URL must be set in app.properties" }
|
||||||
|
|
||||||
|
val appConfigDir = layout.buildDirectory.dir("generated/appconfig/commonMain/kotlin")
|
||||||
|
val generateAppConfig by tasks.registering {
|
||||||
|
group = "build"
|
||||||
|
description = "Generates AppConfig from the shared app.properties"
|
||||||
|
val outputDir = appConfigDir
|
||||||
|
val privacy = privacyPolicyUrl
|
||||||
|
inputs.property("PRIVACY_POLICY_URL", privacy)
|
||||||
|
outputs.dir(outputDir)
|
||||||
|
doLast {
|
||||||
|
val packageDir = outputDir.get().asFile.resolve("com/vnidrop/app")
|
||||||
|
packageDir.mkdirs()
|
||||||
|
fun esc(value: String): String = buildString {
|
||||||
|
for (ch in value) {
|
||||||
|
when (ch) {
|
||||||
|
'\\' -> append("\\\\")
|
||||||
|
'"' -> append("\\\"")
|
||||||
|
'\n' -> append("\\n")
|
||||||
|
'\r' -> append("\\r")
|
||||||
|
'\t' -> append("\\t")
|
||||||
|
'$' -> append("\\$")
|
||||||
|
else -> append(ch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
packageDir.resolve("AppConfig.kt").writeText(
|
||||||
|
"""
|
||||||
|
|package com.vnidrop.app
|
||||||
|
|
|
||||||
|
|/**
|
||||||
|
| * Generated by shared/build.gradle.kts from the shared app.properties.
|
||||||
|
| * Single source of truth for app-wide public constants (also used by Apple).
|
||||||
|
| */
|
||||||
|
|object AppConfig {
|
||||||
|
| const val PRIVACY_POLICY_URL: String = "${esc(privacy)}"
|
||||||
|
|}
|
||||||
|
|
|
||||||
|
""".trimMargin(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
androidTarget {
|
androidTarget {
|
||||||
compilerOptions {
|
compilerOptions {
|
||||||
@@ -122,6 +171,7 @@ kotlin {
|
|||||||
sourceSets {
|
sourceSets {
|
||||||
commonMain {
|
commonMain {
|
||||||
kotlin.srcDir(files(diagnosticsBuildConfigDir).builtBy(generateDiagnosticsBuildConfig))
|
kotlin.srcDir(files(diagnosticsBuildConfigDir).builtBy(generateDiagnosticsBuildConfig))
|
||||||
|
kotlin.srcDir(files(appConfigDir).builtBy(generateAppConfig))
|
||||||
}
|
}
|
||||||
androidMain.dependencies {
|
androidMain.dependencies {
|
||||||
implementation(libs.androidx.activity.compose)
|
implementation(libs.androidx.activity.compose)
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ import vnidrop.shared.generated.resources.os_version_title
|
|||||||
import vnidrop.shared.generated.resources.value_unavailable
|
import vnidrop.shared.generated.resources.value_unavailable
|
||||||
import vnidrop.shared.generated.resources.version_title
|
import vnidrop.shared.generated.resources.version_title
|
||||||
|
|
||||||
private const val PrivacyPolicyUrl = "https://github.com/vnidrop/vnidrop"
|
private val PrivacyPolicyUrl = com.vnidrop.app.AppConfig.PRIVACY_POLICY_URL
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun AboutSettings(
|
internal fun AboutSettings(
|
||||||
|
|||||||
40
shared/src/jvmTest/kotlin/com/vnidrop/app/AppConfigTest.kt
Normal file
40
shared/src/jvmTest/kotlin/com/vnidrop/app/AppConfigTest.kt
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package com.vnidrop.app
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies the build-time [AppConfig] (generated from the shared `app.properties`)
|
||||||
|
* exposes the expected, well-formed values to the shared UI.
|
||||||
|
*/
|
||||||
|
class AppConfigTest {
|
||||||
|
@Test
|
||||||
|
fun privacyPolicyUrlIsTheExpectedHttpsEndpoint() {
|
||||||
|
assertTrue(AppConfig.PRIVACY_POLICY_URL.startsWith("https://"), "must be https")
|
||||||
|
assertEquals("https://vnidrop.sudosy.fr/privacy/", AppConfig.PRIVACY_POLICY_URL)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun privacyPolicyUrlMatchesTheSharedConfigFile() {
|
||||||
|
// Cross-check the generated constant against the single source of truth so a
|
||||||
|
// broken codegen (or drift) is caught, not just a hardcoded copy.
|
||||||
|
assertEquals(privacyUrlFromAppProperties(), AppConfig.PRIVACY_POLICY_URL)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun privacyUrlFromAppProperties(): String {
|
||||||
|
var dir: File? = File(System.getProperty("user.dir")).absoluteFile
|
||||||
|
repeat(8) {
|
||||||
|
val candidate = File(dir, "app.properties")
|
||||||
|
if (candidate.isFile) {
|
||||||
|
return candidate.readLines()
|
||||||
|
.firstOrNull { it.startsWith("PRIVACY_POLICY_URL=") }
|
||||||
|
?.substringAfter("PRIVACY_POLICY_URL=")
|
||||||
|
?: error("PRIVACY_POLICY_URL missing in ${candidate.path}")
|
||||||
|
}
|
||||||
|
dir = dir?.parentFile
|
||||||
|
}
|
||||||
|
error("app.properties not found from ${System.getProperty("user.dir")}")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user