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
4 changes: 2 additions & 2 deletions internal/command/root_sources_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1257,7 +1257,7 @@ func TestYouTubeSearchVideosCallsService(t *testing.T) {
if exitCode != 0 {
t.Fatalf("expected exit code 0, got %d, stderr=%s", exitCode, stderr.String())
}
assertDataOnlyOutputContains(t, stdout.String(), `"data":[]`)
assertCompactTableOutputContains(t, stdout.String(), `"columns":["video_id","title","published_time","duration","view_count","like_count","comment_count","channel_id","channel"]`, `"rows":[]`)
}

func TestInstagramPostCommentsCallsService(t *testing.T) {
Expand Down Expand Up @@ -1297,7 +1297,7 @@ func TestInstagramPostCommentsCallsService(t *testing.T) {
if exitCode != 0 {
t.Fatalf("expected exit code 0, got %d, stderr=%s", exitCode, stderr.String())
}
assertDataOnlyOutputContains(t, stdout.String(), `"comment_id":"c1"`)
assertCompactTableOutputContains(t, stdout.String(), `"columns":["comment_id","text","create_time","like_count","reply_count","author"]`, `"c1"`)
}

func TestInstagramSearchUsersHelpPrintsSchemaFlags(t *testing.T) {
Expand Down
237 changes: 237 additions & 0 deletions internal/output/projection_rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,99 @@ var projectionRules = map[string]projectionRule{
},
},
},
"instagram.get_comment_replies": {
Compact: projectionSpec{
Tables: []tableRule{
{
From: "$root",
To: "$root",
Limit: 20,
Columns: []fieldRule{
{From: "comment_id", To: "comment_id"},
{From: "text", To: "text"},
{From: "create_time", To: "create_time"},
{From: "like_count", To: "like_count"},
{From: "author.username", To: "author"},
},
},
},
},
},
"instagram.get_post_comments": {
Compact: projectionSpec{
Tables: []tableRule{
{
From: "$root",
To: "$root",
Limit: 20,
Columns: []fieldRule{
{From: "comment_id", To: "comment_id"},
{From: "text", To: "text"},
{From: "create_time", To: "create_time"},
{From: "like_count", To: "like_count"},
{From: "reply_count", To: "reply_count"},
{From: "author.username", To: "author"},
},
},
},
},
},
"instagram.get_post_detail": {
Compact: projectionSpec{
Scalars: []fieldRule{
{From: "media_id", To: "media_id"},
{From: "shortcode", To: "shortcode"},
{From: "caption", To: "caption"},
{From: "media_type", To: "media_type"},
{From: "taken_at", To: "taken_at"},
{From: "like_count", To: "like_count"},
{From: "comment_count", To: "comment_count"},
{From: "view_count", To: "view_count"},
{From: "owner.username", To: "owner"},
{From: "permalink", To: "permalink"},
},
},
},
"instagram.get_user_posts": instagramMediaListProjection(),
"instagram.get_user_profile": {
Compact: projectionSpec{
Scalars: []fieldRule{
{From: "user_id", To: "user_id"},
{From: "username", To: "username"},
{From: "full_name", To: "full_name"},
{From: "bio", To: "bio"},
{From: "category", To: "category"},
{From: "follower_count", To: "follower_count"},
{From: "following_count", To: "following_count"},
{From: "media_count", To: "media_count"},
{From: "is_business", To: "is_business"},
{From: "is_verified", To: "is_verified"},
},
},
},
"instagram.get_user_reels": instagramMediaListProjection(),
"instagram.search_hashtags": instagramHashtagListProjection(),
"instagram.search_reels": instagramMediaListProjection(),
"instagram.search_users": {
Compact: projectionSpec{
Tables: []tableRule{
{
From: "$root",
To: "$root",
Limit: 20,
Columns: []fieldRule{
{From: "user_id", To: "user_id"},
{From: "username", To: "username"},
{From: "full_name", To: "full_name"},
{From: "category", To: "category"},
{From: "follower_count", To: "follower_count"},
{From: "media_count", To: "media_count"},
{From: "is_verified", To: "is_verified"},
},
},
},
},
},
"meta_ads.get_ad_detail": {
Compact: projectionSpec{
Scalars: []fieldRule{
Expand Down Expand Up @@ -773,6 +866,81 @@ var projectionRules = map[string]projectionRule{
},
},
},
"youtube.get_channel_detail": {
Compact: projectionSpec{
Scalars: []fieldRule{
{From: "channel_id", To: "channel_id"},
{From: "title", To: "title"},
{From: "handle", To: "handle"},
{From: "description", To: "description"},
{From: "subscriber_count", To: "subscriber_count"},
{From: "video_count", To: "video_count"},
{From: "view_count", To: "view_count"},
{From: "url", To: "url"},
},
},
},
"youtube.get_channel_videos": youtubeVideoListProjection(),
"youtube.get_comment_replies": {
Compact: projectionSpec{
Tables: []tableRule{
{
From: "$root",
To: "$root",
Limit: 20,
Columns: []fieldRule{
{From: "comment_id", To: "comment_id"},
{From: "text", To: "text"},
{From: "create_time", To: "create_time"},
{From: "like_count", To: "like_count"},
{From: "author.name", To: "author"},
},
},
},
},
},
"youtube.get_video_comments": {
Compact: projectionSpec{
Tables: []tableRule{
{
From: "$root",
To: "$root",
Limit: 20,
Columns: []fieldRule{
{From: "comment_id", To: "comment_id"},
{From: "text", To: "text"},
{From: "create_time", To: "create_time"},
{From: "like_count", To: "like_count"},
{From: "reply_count", To: "reply_count"},
{From: "author.name", To: "author"},
},
},
},
},
},
"youtube.get_video_detail": {
Compact: projectionSpec{
Scalars: []fieldRule{
{From: "video_id", To: "video_id"},
{From: "title", To: "title"},
{From: "description", To: "description"},
{From: "published_time", To: "published_time"},
{From: "duration", To: "duration"},
{From: "view_count", To: "view_count"},
{From: "like_count", To: "like_count"},
{From: "comment_count", To: "comment_count"},
{From: "channel.channel_id", To: "channel_id"},
{From: "channel.title", To: "channel"},
{From: "url", To: "url"},
},
},
},
"youtube.get_video_transcript": {
Compact: projectionSpec{
PassThrough: true,
},
},
"youtube.search_videos": youtubeVideoListProjection(),
"xiaohongshu.get_note_comments": {
Compact: projectionSpec{
Tables: []tableRule{
Expand Down Expand Up @@ -823,6 +991,50 @@ var projectionRules = map[string]projectionRule{
},
}

func instagramHashtagListProjection() projectionRule {
return projectionRule{
Compact: projectionSpec{
Tables: []tableRule{
{
From: "$root",
To: "$root",
Limit: 20,
Columns: []fieldRule{
{From: "hashtag_id", To: "hashtag_id"},
{From: "name", To: "name"},
{From: "media_count", To: "media_count"},
},
},
},
},
}
}

func instagramMediaListProjection() projectionRule {
return projectionRule{
Compact: projectionSpec{
Tables: []tableRule{
{
From: "$root",
To: "$root",
Limit: 10,
Columns: []fieldRule{
{From: "media_id", To: "media_id"},
{From: "shortcode", To: "shortcode"},
{From: "caption", To: "caption"},
{From: "media_type", To: "media_type"},
{From: "taken_at", To: "taken_at"},
{From: "like_count", To: "like_count"},
{From: "comment_count", To: "comment_count"},
{From: "view_count", To: "view_count"},
{From: "owner.username", To: "owner"},
},
},
},
},
}
}

func videoDetailScalarFields(identityField string) []fieldRule {
return []fieldRule{
{From: "platform", To: "platform"},
Expand All @@ -842,3 +1054,28 @@ func videoDetailScalarFields(identityField string) []fieldRule {
{From: "music", To: "music"},
}
}

func youtubeVideoListProjection() projectionRule {
return projectionRule{
Compact: projectionSpec{
Tables: []tableRule{
{
From: "$root",
To: "$root",
Limit: 10,
Columns: []fieldRule{
{From: "video_id", To: "video_id"},
{From: "title", To: "title"},
{From: "published_time", To: "published_time"},
{From: "duration", To: "duration"},
{From: "view_count", To: "view_count"},
{From: "like_count", To: "like_count"},
{From: "comment_count", To: "comment_count"},
{From: "channel.channel_id", To: "channel_id"},
{From: "channel.title", To: "channel"},
},
},
},
},
}
}
79 changes: 79 additions & 0 deletions internal/output/projection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -782,6 +782,69 @@ func TestProjectCapabilityAutoFallsBackToDataWhenUnsupported(t *testing.T) {
}
}

func TestInstagramSearchReelsCompactProjectionDropsMediaURLs(t *testing.T) {
payload := json.RawMessage(`[
{
"media_id":"m1",
"shortcode":"ABC123",
"caption":"desk setup",
"media_type":"video",
"media_url":"https://cdn.example/video.mp4",
"thumbnail":"https://cdn.example/thumb.jpg",
"permalink":"https://www.instagram.com/reel/ABC123/",
"taken_at":"2026-06-28T00:00:00Z",
"like_count":10,
"comment_count":2,
"view_count":100,
"owner":{"username":"creator","profile_pic_url":"https://cdn.example/avatar.jpg"}
}
]`)
body, err := projectCapability("instagram.search_reels", payload, FormatCompact)
if err != nil {
t.Fatalf("projectCapability() error = %v", err)
}
text := string(body)
if !strings.Contains(text, `"columns":["media_id","shortcode","caption","media_type","taken_at","like_count","comment_count","view_count","owner"]`) {
t.Fatalf("expected compact instagram reel columns, got %s", text)
}
for _, unwanted := range []string{"media_url", "thumbnail", "permalink", "cdn.example", "profile_pic_url"} {
if strings.Contains(text, unwanted) {
t.Fatalf("expected compact instagram reel output to drop %s, got %s", unwanted, text)
}
}
}

func TestYouTubeSearchVideosCompactProjectionDropsURLs(t *testing.T) {
payload := json.RawMessage(`[
{
"video_id":"v1",
"title":"demo",
"url":"https://www.youtube.com/watch?v=v1",
"description":"long snippet",
"thumbnail":"https://i.ytimg.com/vi/v1/default.jpg",
"published_time":"2026-06-28",
"duration":"1:23",
"view_count":1000,
"like_count":10,
"comment_count":2,
"channel":{"channel_id":"c1","title":"Channel","url":"https://www.youtube.com/@channel"}
}
]`)
body, err := projectCapability("youtube.search_videos", payload, FormatCompact)
if err != nil {
t.Fatalf("projectCapability() error = %v", err)
}
text := string(body)
if !strings.Contains(text, `"columns":["video_id","title","published_time","duration","view_count","like_count","comment_count","channel_id","channel"]`) {
t.Fatalf("expected compact youtube video columns, got %s", text)
}
for _, unwanted := range []string{"url", "thumbnail", "description", "youtube.com", "ytimg.com"} {
if strings.Contains(text, unwanted) {
t.Fatalf("expected compact youtube search output to drop %s, got %s", unwanted, text)
}
}
}

func TestProjectionRulesCoverAllAgentTestCapabilities(t *testing.T) {
required := []string{
"amazon.get_product",
Expand Down Expand Up @@ -825,6 +888,22 @@ func TestProjectionRulesCoverAllAgentTestCapabilities(t *testing.T) {
"tiktok.product_reviews",
"xiaohongshu.get_note_comments",
"douyin.get_comment_replies",
"instagram.search_users",
"instagram.search_hashtags",
"instagram.search_reels",
"instagram.get_user_profile",
"instagram.get_user_posts",
"instagram.get_user_reels",
"instagram.get_post_detail",
"instagram.get_post_comments",
"instagram.get_comment_replies",
"youtube.search_videos",
"youtube.get_video_detail",
"youtube.get_video_comments",
"youtube.get_comment_replies",
"youtube.get_channel_detail",
"youtube.get_channel_videos",
"youtube.get_video_transcript",
}
if len(projectionRules) != len(required) {
t.Fatalf("projectionRules size = %d, want %d", len(projectionRules), len(required))
Expand Down
Loading