-
Notifications
You must be signed in to change notification settings - Fork 0
feat(contacts): add group management and starring operations #101
Description
Context
Following the Gmail organizational operations (#98), this adds non-destructive organizational operations for Google Contacts: managing contact group memberships and starring contacts. Reuses the internal/bulk/ package and --ids pattern established in #98.
Depends on #98 for: internal/bulk/ package, architecture test allowlist, scope migration infrastructure, --ids flag pattern.
Scope Change
internal/auth/auth.go: Replace people.ContactsReadonlyScope with people.ContactsScope (full contacts read/write). The architecture test allowlist (from #98) already permits this scope.
New API Client Methods
internal/contacts/client.go:
func (c *Client) AddToGroup(ctx context.Context, groupResourceName string, contactResourceNames []string) error
func (c *Client) RemoveFromGroup(ctx context.Context, groupResourceName string, contactResourceNames []string) error
func (c *Client) ResolveGroupName(ctx context.Context, name string) (string, error)
func (c *Client) SearchContactIDs(ctx context.Context, query string, pageSize int64) ([]string, error)Group membership uses c.service.ContactGroups.Members.Modify().
Starring contacts: Implemented by adding to/removing from the system group contactGroups/starred — so star/unstar reuse AddToGroup/RemoveFromGroup with that resource name.
ResolveGroupName maps user-friendly group names to resource names (e.g., "Friends" → contactGroups/abc123).
New Interface Methods
Add to ContactsClient in internal/cmd/contacts/output.go:
AddToGroup(ctx context.Context, groupResourceName string, contactResourceNames []string) error
RemoveFromGroup(ctx context.Context, groupResourceName string, contactResourceNames []string) error
ResolveGroupName(ctx context.Context, name string) (string, error)
SearchContactIDs(ctx context.Context, query string, pageSize int64) ([]string, error)New Commands
| Command | Description |
|---|---|
gro contacts add-to-group <group-name> <contact-ids...> |
Add contacts to a group |
gro contacts remove-from-group <group-name> <contact-ids...> |
Remove contacts from a group |
gro contacts star <contact-ids...> |
Star contacts (add to system "Starred" group) |
gro contacts unstar <contact-ids...> |
Unstar contacts |
All support bulk input modes (--stdin, --query, positional args), --json, --dry-run. Reuses internal/bulk/.
Also add --ids flag to gro contacts list and gro contacts search for piping:
gro contacts search "John" --ids | gro contacts add-to-group "Friends" --stdin
gro contacts list --max 10 --ids | gro contacts star --stdin --dry-runFiles to Create
| File | Purpose |
|---|---|
internal/cmd/contacts/group_manage.go |
Add-to-group / remove-from-group commands |
internal/cmd/contacts/group_manage_test.go |
Tests |
internal/cmd/contacts/star.go |
Star/unstar commands |
internal/cmd/contacts/star_test.go |
Tests |
Files to Modify
| File | Change |
|---|---|
internal/auth/auth.go |
Replace contacts readonly scope with contacts scope |
internal/auth/auth_test.go |
Update scope count/assertions |
internal/architecture/architecture_test.go |
Update allowlist (already present from #98) |
internal/contacts/client.go |
Add AddToGroup, RemoveFromGroup, ResolveGroupName, SearchContactIDs methods |
internal/cmd/contacts/output.go |
Extend ContactsClient interface |
internal/cmd/contacts/mock_test.go |
Add mock function fields |
internal/cmd/contacts/contacts.go |
Register new subcommands |
internal/cmd/contacts/list.go |
Add --ids flag |
internal/cmd/contacts/search.go |
Add --ids flag |
integration-tests.md |
Add contacts organizational test section |
README.md |
Add contacts organizational operations examples |
Integration Tests
Group Management
| Test Case | Command | Expected |
|---|---|---|
| Add to group | CONTACT=$(gro ppl list --max 1 --ids); gro contacts add-to-group "Friends" "$CONTACT" |
"Added 1 contact(s) to group 'Friends'." |
| Add via pipe | gro ppl search "John" --ids | gro contacts add-to-group "Work" --stdin |
"Added N contact(s) to group 'Work'." |
| Remove from group | gro contacts remove-from-group "Friends" "$CONTACT" |
"Removed 1 contact(s) from group 'Friends'." |
| Dry run | gro contacts add-to-group "Friends" --query "John" --dry-run |
"[dry-run] Would add N contact(s) to group 'Friends'." |
| Invalid group | gro contacts add-to-group "NonexistentGroup12345" "$CONTACT" |
Error: group not found |
| JSON output | gro contacts add-to-group "Friends" "$CONTACT" --json |
Valid JSON result |
Starring
| Test Case | Command | Expected |
|---|---|---|
| Star contact | CONTACT=$(gro ppl list --max 1 --ids); gro contacts star "$CONTACT" |
"Starred 1 contact(s)." |
| Star via pipe | gro ppl search "John" --ids | gro contacts star --stdin |
"Starred N contact(s)." |
| Unstar | gro contacts unstar "$CONTACT" |
"Unstarred 1 contact(s)." |
| Dry run | gro contacts star --query "John" --dry-run |
"[dry-run] Would star N contact(s)." |
--ids Flag
| Test Case | Command | Expected |
|---|---|---|
| List --ids | gro ppl list --max 3 --ids |
3 bare resource names, one per line |
| Search --ids | gro ppl search "test" --ids |
Bare resource names, one per line |
| --ids and --json exclusive | gro ppl list --ids --json |
Error: mutually exclusive |
Key Risks
- Contacts starring mechanism — verify that
contactGroups/starredis the correct system group resource name for starring contacts via the People API. - Group member modification API — the People API's group member modification may have batch limits similar to Gmail's BatchModify. Check and implement chunking if needed.
people.ContactsScopebreadth — this is a broad scope (full read/write). The architecture test allowlist and forbidden-methods test provide defense-in-depth against accidental destructive operations.