diff --git a/README.md b/README.md index b4f1c56..b7b2dff 100644 --- a/README.md +++ b/README.md @@ -204,7 +204,7 @@ mailtrap domains list --output text | **Permissions** | `permissions resources`, `permissions bulk-update` | | **Tokens** | `tokens list`, `tokens get`, `tokens create [--expires-at]`, `tokens delete`, `tokens reset [--expires-at]` | | **Billing** | `billing usage` | -| **Organizations** | `organizations list-sub-accounts`, `organizations create-sub-account` | +| **Organizations** | `organizations list-sub-accounts`, `organizations create-sub-account`, `organizations delete-sub-account` | | **Config** | `configure`, `completion [bash\|zsh\|fish\|powershell]` | ## Shell Completion diff --git a/docs/TEST_PLAN.md b/docs/TEST_PLAN.md index 74e558e..b10b31e 100644 --- a/docs/TEST_PLAN.md +++ b/docs/TEST_PLAN.md @@ -325,8 +325,9 @@ Prerequisite: Send an email with an attachment to the sandbox. | 20.1 | List sub-accounts | `mailtrap organizations list-sub-accounts` | Table with sub-accounts | | 20.2 | Create sub-account | `mailtrap organizations create-sub-account --name "test-sub"` | New sub-account | | 20.3 | Missing name | `mailtrap organizations create-sub-account` | Error: `--name is required` | +| 20.4 | Delete sub-account | `mailtrap organizations delete-sub-account --org-id --sub-account-id ` | Success message; repeated call returns 404 | -**Caution:** Creating sub-accounts may have billing implications. +**Caution:** Creating sub-accounts may have billing implications. Deleting a sub-account is permanent and removes all of its data; deleting the organization's last sub-account also deletes the organization. Only delete the sub-account created in 20.2. ## 21. Configure @@ -401,7 +402,7 @@ Run tests in dependency order so earlier tests create resources needed by later 17. **Permissions** (read/update) 18. **Tokens** (CRUD — may need admin token) 19. **Billing** (read-only) -20. **Organizations** (read-only, skip create unless safe) +20. **Organizations** (read-only, skip create/delete unless safe – delete only the sub-account created in 20.2) 21. **Configure** (local config only) 22. **Inbound** (CRUD for folders/inboxes; messages/threads need received mail) @@ -430,6 +431,6 @@ Run tests in dependency order so earlier tests create resources needed by later | Permissions | 2 | 3 | | Tokens | 5 | 6 | | Billing | 1 | 2 | -| Organizations | 2 | 3 | +| Organizations | 3 | 4 | | Configure | 1 | 2 | -| **Total** | **~90** | **~107** | +| **Total** | **~91** | **~108** | diff --git a/internal/commands/organizations/delete_sub_account.go b/internal/commands/organizations/delete_sub_account.go new file mode 100644 index 0000000..ac5fb15 --- /dev/null +++ b/internal/commands/organizations/delete_sub_account.go @@ -0,0 +1,54 @@ +package organizations + +import ( + "context" + "fmt" + + "github.com/mailtrap/mailtrap-cli/internal/client" + "github.com/mailtrap/mailtrap-cli/internal/cmdutil" + "github.com/spf13/cobra" +) + +func NewCmdDeleteSubAccount(f *cmdutil.Factory) *cobra.Command { + var orgID string + var subAccountID string + + cmd := &cobra.Command{ + Use: "delete-sub-account", + Short: "Delete a sub-account from an organization", + Long: `Delete a sub-account from an organization. Requires sub-account management permissions for the organization. + +The deletion is permanent: the sub-account and all of its data are removed and cannot be restored. +Deleting the organization's last sub-account also deletes the organization. + +A repeated call for the same sub-account returns 404. Rate limit: 10 requests per minute per organization.`, + RunE: func(cmd *cobra.Command, args []string) error { + if err := cmdutil.RequireFlag("org-id", orgID); err != nil { + return err + } + if err := cmdutil.RequireFlag("sub-account-id", subAccountID); err != nil { + return err + } + + c, err := f.NewClient() + if err != nil { + return err + } + + path := fmt.Sprintf("/api/organizations/%s/sub_accounts/%s", orgID, subAccountID) + + if err := c.Delete(context.Background(), client.BaseGeneral, path, nil); err != nil { + return err + } + + fmt.Fprintf(f.IOStreams.Out, "Sub-account %s deleted successfully\n", subAccountID) + + return nil + }, + } + + cmd.Flags().StringVar(&orgID, "org-id", "", "Organization ID") + cmd.Flags().StringVar(&subAccountID, "sub-account-id", "", "Sub-account ID") + + return cmd +} diff --git a/internal/commands/organizations/organizations.go b/internal/commands/organizations/organizations.go index 5e8d4f0..0883765 100644 --- a/internal/commands/organizations/organizations.go +++ b/internal/commands/organizations/organizations.go @@ -13,6 +13,7 @@ func NewCmdOrganizations(f *cmdutil.Factory) *cobra.Command { cmd.AddCommand(NewCmdListSubAccounts(f)) cmd.AddCommand(NewCmdCreateSubAccount(f)) + cmd.AddCommand(NewCmdDeleteSubAccount(f)) return cmd } diff --git a/internal/commands/organizations/organizations_test.go b/internal/commands/organizations/organizations_test.go index 7ebdf1e..caf8b41 100644 --- a/internal/commands/organizations/organizations_test.go +++ b/internal/commands/organizations/organizations_test.go @@ -150,3 +150,51 @@ func TestCreateSubAccountMissingFlags(t *testing.T) { t.Fatal("expected error when required flags are missing") } } + +func TestDeleteSubAccount(t *testing.T) { + f, buf, cleanup := setupTest(func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodDelete { + t.Errorf("expected DELETE, got %s", r.Method) + } + if !strings.HasSuffix(r.URL.Path, "/api/organizations/456/sub_accounts/789") { + t.Errorf("unexpected path: %s", r.URL.Path) + } + + w.WriteHeader(http.StatusNoContent) + }) + defer cleanup() + + cmd := organizations.NewCmdOrganizations(f) + cmd.SetArgs([]string{"delete-sub-account", "--org-id", "456", "--sub-account-id", "789"}) + cmd.SetOut(buf) + + err := cmd.Execute() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + output := buf.String() + if !strings.Contains(output, "Sub-account 789 deleted successfully") { + t.Errorf("expected output to contain 'Sub-account 789 deleted successfully', got:\n%s", output) + } +} + +func TestDeleteSubAccountMissingFlags(t *testing.T) { + f, _, cleanup := setupTest(func(w http.ResponseWriter, r *http.Request) {}) + defer cleanup() + + buf := &bytes.Buffer{} + f.IOStreams.Out = buf + + cmd := organizations.NewCmdOrganizations(f) + cmd.SetArgs([]string{"delete-sub-account", "--org-id", "456"}) + cmd.SetOut(buf) + + err := cmd.Execute() + if err == nil { + t.Fatal("expected error when --sub-account-id is missing") + } + if !strings.Contains(err.Error(), "--sub-account-id is required") { + t.Errorf("expected '--sub-account-id is required' error, got: %v", err) + } +} diff --git a/skill-evals/mailtrap-cli/evals.json b/skill-evals/mailtrap-cli/evals.json index 333e1c8..9c4512e 100644 --- a/skill-evals/mailtrap-cli/evals.json +++ b/skill-evals/mailtrap-cli/evals.json @@ -170,6 +170,15 @@ "must_not_contain": ["accounts", "list-sub-accounts"] } }, + { + "name": "delete_sub_account", + "prompt": "Remove sub-account 12347 from organization 2000002", + "expected": { + "command": "organizations delete-sub-account", + "flags": ["--org-id", "--sub-account-id"], + "must_not_contain": ["accounts delete", "account-access remove", "list-sub-accounts"] + } + }, { "name": "send_with_cc_bcc", "prompt": "Send a transactional email from boss@acme.com to team@acme.com, CC manager@acme.com, BCC hr@acme.com with subject 'Update'", diff --git a/skills/mailtrap-cli/references/accounts.md b/skills/mailtrap-cli/references/accounts.md index 7bc2730..05aae01 100644 --- a/skills/mailtrap-cli/references/accounts.md +++ b/skills/mailtrap-cli/references/accounts.md @@ -132,3 +132,16 @@ Create a sub-account under an organization. |------|------|----------|-------------| | `--org-id` | string | Yes | Organization ID | | `--name` | string | Yes | Sub-account name | + +--- + +## organizations delete-sub-account + +Delete a sub-account from an organization. Requires sub-account management permissions for the organization. + +| Flag | Type | Required | Description | +|------|------|----------|-------------| +| `--org-id` | string | Yes | Organization ID | +| `--sub-account-id` | string | Yes | Sub-account ID | + +**Caution:** Permanent – the sub-account and all of its data are removed and cannot be restored. Deleting the organization's last sub-account also deletes the organization. A repeated call for the same sub-account returns 404. Rate limit: 10 requests per minute per organization.