Skip to content

Drop legacy internal teams (script + tests)#525

Open
bonus414 wants to merge 8 commits into
devfrom
chore/drop-legacy-internal-teams
Open

Drop legacy internal teams (script + tests)#525
bonus414 wants to merge 8 commits into
devfrom
chore/drop-legacy-internal-teams

Conversation

@bonus414
Copy link
Copy Markdown
Collaborator

@bonus414 bonus414 commented May 20, 2026

AI created so we'll need a review

What

Adds scripts/drop_legacy_internal_teams.py — a runbook-style script to identify and soft-delete legacy amazee-internal teams from the old dashboard.

Closes https://github.com/amazeeio/moad/issues/350

Why

Legacy internal teams (Henk's team, Ricardo's team, etc.) create inconsistent state between the old dashboard and Moad. Old dashboard interactions cause the amazee.ai DB to drift from LiteLLM. We're moving to two team types only (Stripe-paying / non-Stripe partner), so this cruft needs to go.

Affected users should sign in to Moad with their amazee email — they'll see the internal workspace and can get keys from there.

How it works

  1. Identify — finds teams where admin_email LIKE %@amazee.io% (configurable via --email-pattern, or target specific IDs with --team-ids)
  2. Report — prints activity summary: user count, key count, products, last key activity, Stripe status
  3. Soft-delete — uses the existing soft_delete_team() service function (sets deleted_at, deactivates users, expires all keys in LiteLLM)
  4. Safety — refuses to delete teams that have active Stripe product associations
  5. Reversible — soft-deleted teams can be restored via the existing /restore endpoint; hard-delete happens automatically after 60 days via the retention job

Usage

# Dry run (default) — just list what would be deleted
python scripts/drop_legacy_internal_teams.py

# Actually delete
python scripts/drop_legacy_internal_teams.py --execute

# Target specific team IDs
python scripts/drop_legacy_internal_teams.py --team-ids 12 34 56 --execute

Tests

tests/test_drop_legacy_internal_teams.py covers:

  • Email pattern matching
  • Exclusion of already-deleted teams
  • Explicit team ID filtering
  • Summary generation
  • Dry-run safety (no deletion)
  • Execute mode (calls soft_delete_team)
  • Product association safety gate

Checklist (from ticket)

  • List of internal legacy teams identified (script does this)
  • Confirmed inactive (summary shows key activity, products, etc.)
  • Teams removed from legacy system (soft-delete via --execute)
  • People notified to use Moad internal workspace (manual step after running script)

Greptile Summary

Adds a runbook-style script (scripts/drop_legacy_internal_teams.py) and accompanying tests to identify and soft-delete legacy amazee-internal teams, using the existing soft_delete_team service function with a dry-run default and a product-association safety gate.

  • The total_keys field in the team summary double-counts keys that carry both team_id and an owner_id belonging to the same team, because two additive .count() queries are used instead of a single OR-based query.
  • The email-pattern LIKE query does not escape . or _, so the default @amazee.io pattern technically matches any single character in place of the dot.
  • When both --team-ids and --email-pattern are provided, the email pattern is silently discarded.

Confidence Score: 4/5

Safe to merge; the script is a human-in-the-loop runbook with a dry-run default, and the actual deletion path is guarded by the product-association check.

The double-counting in total_keys could show an operator a misleadingly high key count for a team, potentially causing them to hesitate on a team that should be deleted. The deletion logic itself is unaffected, but the summary the human reviews before typing --execute may be inaccurate.

scripts/drop_legacy_internal_teams.py — specifically the get_team_summary key-counting logic and the LIKE filter in find_internal_teams.

Important Files Changed

Filename Overview
scripts/drop_legacy_internal_teams.py Runbook script to identify and soft-delete legacy amazee-internal teams; contains a double-counting bug in the total_keys summary field and a minor LIKE-wildcard caveat in the email pattern.
tests/test_drop_legacy_internal_teams.py Good test coverage for all major code paths (dry-run, execute, product safety gate, email pattern, explicit IDs); no issues found.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A([Start]) --> B[Parse args]
    B --> C{--team-ids provided?}
    C -- Yes --> D[Filter by explicit IDs]
    C -- No --> E[Filter by email pattern]
    D --> F[Exclude already deleted]
    E --> F
    F --> G[Build summary per team]
    G --> H[Print summary table]
    H --> I{Teams found?}
    I -- No --> Z([Exit])
    I -- Yes --> J{--execute flag?}
    J -- No --> K[DRY RUN exit]
    J -- Yes --> L{Any team has products?}
    L -- Yes --> M[sys.exit 1]
    L -- No --> N[soft_delete_team loop]
    N --> O{Exception?}
    O -- Yes --> P[Log failure / continue]
    O -- No --> Q[Log success]
    P --> R{More teams?}
    Q --> R
    R -- Yes --> N
    R -- No --> S[Print done]
    S --> Z
Loading

Reviews (1): Last reviewed commit: "Add script to drop legacy internal teams" | Re-trigger Greptile

Greptile also left 3 inline comments on this PR.

Adds scripts/drop_legacy_internal_teams.py which:
- Identifies internal teams by admin_email pattern (@amazee.io)
- Shows activity summary (users, keys, products, last activity)
- Soft-deletes them via the existing soft_delete_team() service
- Refuses to delete teams with active Stripe products
- Supports dry-run (default), --execute, --team-ids, --email-pattern

Soft-delete is reversible: sets deleted_at, deactivates users, expires
keys in LiteLLM. Hard-delete happens automatically after 60 days via
the existing retention job.

Closes amazeeio/moad#350
Comment thread scripts/drop_legacy_internal_teams.py Fixed
Comment thread tests/test_drop_legacy_internal_teams.py Fixed
Comment thread tests/test_drop_legacy_internal_teams.py Fixed
Comment thread scripts/drop_legacy_internal_teams.py Outdated
Comment thread scripts/drop_legacy_internal_teams.py
Comment on lines +195 to +205
"\n Remove their product associations first, or exclude them with --team-ids.\n"
)
sys.exit(1)

print(f" 🗑️ Soft-deleting {len(teams)} team(s)...\n")

succeeded = []
failed = []

for team in teams:
try:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 --email-pattern silently ignored when --team-ids is supplied

Both flags are accepted by the CLI, but when --team-ids is present the email pattern is never applied — the else branch is skipped entirely. A user who passes --email-pattern @someother.io --team-ids 12 34 expecting extra filtering will see all non-deleted teams with those IDs, not just ones matching the pattern. At minimum a warning would help, or the help text could state that --team-ids supersedes --email-pattern.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an operator-facing cleanup script to identify and soft-delete legacy “amazee-internal” teams (primarily by admin email pattern), along with tests to validate identification/reporting and safe execution behavior.

Changes:

  • Introduces scripts/drop_legacy_internal_teams.py to find, summarize, and (optionally) soft-delete legacy internal teams via soft_delete_team().
  • Adds tests/test_drop_legacy_internal_teams.py to cover team selection, summary counts, dry-run safety, execute mode behavior, and product-association gating.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
scripts/drop_legacy_internal_teams.py New runbook-style script to identify/report legacy internal teams and soft-delete them with safety gates.
tests/test_drop_legacy_internal_teams.py New unit tests for the script’s selection logic, summary generation, and delete safety behavior.
Comments suppressed due to low confidence (1)

tests/test_drop_legacy_internal_teams.py:26

  • import sys, os on one line violates ruff’s E401 (multiple imports on one line). Split these into separate import statements (and keep imports grouped consistently).
# Import the functions under test — the script lives one level up from tests/
import sys, os


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/test_drop_legacy_internal_teams.py Outdated
Comment thread scripts/drop_legacy_internal_teams.py Outdated
Comment on lines +181 to +187
# Safety check: refuse to delete teams with active Stripe products
teams_with_products = []
for team in teams:
product_count = (
db.query(DBTeamProduct).filter(DBTeamProduct.team_id == team.id).count()
)
if product_count > 0:
Comment on lines +204 to +213
for team in teams:
try:
await soft_delete_team(db, team)
succeeded.append(team)
print(f" ✅ [{team.id}] {team.name} — soft-deleted")
except Exception as e:
failed.append((team, str(e)))
print(f" ❌ [{team.id}] {team.name} — FAILED: {e}")

print(f"\n Done. {len(succeeded)} succeeded, {len(failed)} failed.\n")
await drop_teams(db, [team], dry_run=False)

assert exc_info.value.code == 1
mock_soft_delete.assert_not_called()
dan2k3k4 and others added 5 commits May 20, 2026 17:07
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Comment thread scripts/drop_legacy_internal_teams.py Fixed
Copy link
Copy Markdown
Contributor

@dspachos dspachos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • CI is failing
  • There are comment by greptile and copilot that should be addressed

bonus414 added 2 commits May 27, 2026 19:20
- Remove unused datetime import (CodeQL)
- Filter product safety check on DBProduct.active only (Copilot)
- Exit non-zero when soft-delete partially fails (Copilot)
- Warn when --email-pattern is ignored with --team-ids (Greptile)
- Split 'import sys, os' into separate lines (lint)
- Add test: inactive product associations don't block deletion
…can import

The test imports from scripts/drop_legacy_internal_teams.py via sys.path,
but the Docker test runner only volume-mounted app/ and tests/. Added
scripts/ mount to all three backend-test targets.
@bonus414
Copy link
Copy Markdown
Collaborator Author

@dspachos had AI make the changes and seems the tests are now passing

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.

4 participants