Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
90 changes: 90 additions & 0 deletions .github/scripts/test_update_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,99 @@ class Args:
assert all(update_registry.kit_version(b) is not None for b in entry["binaries"])


def _args(version, pkv, keep=2):
class Args:
id = "com.TablePro.DynamoDBDriverPlugin"
name = "DynamoDB"
summary = "new"
db_type_ids = '["dynamodb"]'
arm64_url = "https://x/arm64"
arm64_sha = "a"
x86_64_url = "https://x/x86_64"
x86_64_sha = "b"
min_app_version = "0.43.0"
icon = "icon"
homepage = "https://tablepro.app"
category = "database-driver"

Args.version = version
Args.plugin_kit_version = pkv
Args.keep_kit_versions = keep
return Args


def _manifest(*kit_versions):
return {
"schemaVersion": 2,
"plugins": [
{
"id": "com.TablePro.DynamoDBDriverPlugin",
"name": "DynamoDB",
"version": "1.0.15",
"summary": "old",
"category": "database-driver",
"binaries": [
{
"architecture": arch,
"pluginKitVersion": pkv,
"downloadURL": f"old-{pkv}-{arch}",
"sha256": "x",
}
for pkv in kit_versions
for arch in ("arm64", "x86_64")
],
}
],
}


def test_publishing_a_new_kit_version_keeps_the_previous_one():
"""The retention policy's whole point: a user on the previous app can still install.

Nothing covered this. The one existing merge test starts from a null-version binary, which
is dropped rather than retained, so the surviving-binary path was never exercised.
"""
result = update_registry.update_plugin_entry(_manifest(14), _args("1.0.16", 15))
entry = next(p for p in result["plugins"] if p["id"] == "com.TablePro.DynamoDBDriverPlugin")
kits = sorted(update_registry.kit_version(b) for b in entry["binaries"])
assert kits == [14, 14, 15, 15], kits
urls = {b["downloadURL"] for b in entry["binaries"]}
assert "old-14-arm64" in urls, urls


def test_a_third_kit_version_evicts_the_oldest():
result = update_registry.update_plugin_entry(_manifest(13, 14), _args("1.0.17", 15))
entry = next(p for p in result["plugins"] if p["id"] == "com.TablePro.DynamoDBDriverPlugin")
kits = sorted(update_registry.kit_version(b) for b in entry["binaries"])
assert kits == [14, 14, 15, 15], kits


def test_republishing_the_same_kit_version_replaces_its_binaries():
result = update_registry.update_plugin_entry(_manifest(14), _args("1.0.16", 14))
entry = next(p for p in result["plugins"] if p["id"] == "com.TablePro.DynamoDBDriverPlugin")
kits = sorted(update_registry.kit_version(b) for b in entry["binaries"])
assert kits == [14, 14], kits
urls = {b["downloadURL"] for b in entry["binaries"]}
assert urls == {"https://x/arm64", "https://x/x86_64"}, urls


def test_publishing_below_the_retained_window_refuses():
"""Otherwise the entry advertises a version whose binary the prune just discarded."""
try:
update_registry.update_plugin_entry(_manifest(15, 16), _args("1.0.18", 14))
except SystemExit as error:
assert "older than the" in str(error), error
else:
raise AssertionError("expected SystemExit when the new binary would be pruned away")


if __name__ == "__main__":
test_kit_version_rejects_non_int()
test_prune_drops_null_kit_binary()
test_prune_keeps_only_two_newest()
test_update_entry_drops_legacy_null_binary()
test_publishing_a_new_kit_version_keeps_the_previous_one()
test_a_third_kit_version_evicts_the_oldest()
test_republishing_the_same_kit_version_replaces_its_binaries()
test_publishing_below_the_retained_window_refuses()
print("All update-registry tests passed.")
27 changes: 17 additions & 10 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -229,17 +229,24 @@ jobs:
echo "Contents of artifacts directory:"
ls -la artifacts/

# Note: DMG files should already have correct names from build
# ZIP files need to be renamed

# Rename ZIP files if they exist
if [ -f "artifacts/TablePro-arm64.zip" ]; then
mv artifacts/TablePro-arm64.zip "artifacts/TablePro-${VERSION}-arm64.zip"
fi
# The DMGs are already versioned by package-artifacts.sh; the ZIPs are not.
# Both renames used to be guarded by `if [ -f ... ]` with no else, so a missing or
# differently named ZIP printed "organized successfully" and the release published
# whatever the upload glob happened to match.
for ARCH in arm64 x86_64; do
if [ ! -f "artifacts/TablePro-${ARCH}.zip" ]; then
echo "::error::artifacts/TablePro-${ARCH}.zip is missing; the ${ARCH} build did not produce an update archive"
ls -la artifacts/ >&2
exit 1
fi
mv "artifacts/TablePro-${ARCH}.zip" "artifacts/TablePro-${VERSION}-${ARCH}.zip"

if [ -f "artifacts/TablePro-x86_64.zip" ]; then
mv artifacts/TablePro-x86_64.zip "artifacts/TablePro-${VERSION}-x86_64.zip"
fi
if [ ! -f "artifacts/TablePro-${VERSION}-${ARCH}.dmg" ]; then
echo "::error::artifacts/TablePro-${VERSION}-${ARCH}.dmg is missing"
ls -la artifacts/ >&2
exit 1
fi
done

echo "✅ Artifacts organized successfully"
echo "Final artifacts:"
Expand Down
34 changes: 30 additions & 4 deletions scripts/ci/verify-build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,37 @@ if [ ! -d "$PLUGINS_DIR" ]; then
fi
echo "✅ PlugIns directory exists"

REQUIRED_PLUGINS=(
"MySQLDriver.tableplugin"
"PostgreSQLDriver.tableplugin"
"SQLiteDriver.tableplugin"
# Derived from project.yml's copy phase, which is what decides what ends up in the bundle.
# This used to be a hardcoded list of three, so a release could ship missing eleven of the
# fourteen embedded plugins and still print "All bundled plugin bundles present".
REQUIRED_PLUGINS=()
while IFS= read -r plugin; do
REQUIRED_PLUGINS+=("${plugin}.tableplugin")
done < <(python3 -c '
import re, sys

# Parsed without PyYAML on purpose: this runs on the release path and the module is not part of
# a stock runner image. The shape being read is fixed and small:
#
# - target: MySQLDriver
# copy:
# destination: plugins
#
text = open("project.yml").read()
names = re.findall(
r"-\s*target:\s*(\S+)[^\n]*\n(?:\s+\w+:[^\n]*\n)*?\s*copy:\s*\{\s*destination:\s*plugins\s*\}",
text,
)
if not names:
sys.exit("project.yml declares no plugins copied into the app bundle")
print("\n".join(names))
')

if [ "${#REQUIRED_PLUGINS[@]}" -eq 0 ]; then
echo "❌ ERROR: could not read the bundled plugin list from project.yml"
exit 1
fi
echo "project.yml embeds ${#REQUIRED_PLUGINS[@]} plugins"

MISSING_PLUGINS=0
for PLUGIN in "${REQUIRED_PLUGINS[@]}"; do
Expand Down
Loading