11#!/usr/bin/env python3
22"""Tests for cards/render.py - YAML template rendering."""
33
4+ from typing import Any
5+
46from cards .render import render
57from cards .render import yaml_escape_sq
8+ from db import DiscussionStats
69from db import GitlabUser
710from db import MergeRequestExtraState
811from db import MergeRequestInfos
@@ -21,6 +24,9 @@ def make_mri(
2124 assignees : list [GLUser ] | None = None ,
2225 reviewers : list [GLUser ] | None = None ,
2326 approvers : list [str ] | None = None ,
27+ action : str = "open" ,
28+ draft : bool = False ,
29+ discussion_stats : DiscussionStats | None = None ,
2430) -> MergeRequestInfos :
2531 """Create a MergeRequestInfos for testing."""
2632 payload = MergeRequestPayload (
@@ -46,10 +52,10 @@ def make_mri(
4652 iid = 1 ,
4753 title = title ,
4854 created_at = "2025-01-01 00:00:00 UTC" ,
49- draft = False ,
55+ draft = draft ,
5056 state = "opened" ,
5157 url = "https://gitlab.example.com/test/project/-/merge_requests/1" ,
52- action = "open" ,
58+ action = action ,
5359 updated_at = "2025-01-01 00:00:00 UTC" ,
5460 detailed_merge_status = "mergeable" ,
5561 head_pipeline_id = None ,
@@ -70,6 +76,7 @@ def make_mri(
7076 approvers = {},
7177 pipeline_statuses = {},
7278 emojis = {},
79+ discussion_stats = discussion_stats ,
7380 )
7481
7582 if approvers :
@@ -215,3 +222,67 @@ def test_fingerprint_is_sha256(self):
215222 fp = compute_mri_fingerprint (mri )
216223 assert len (fp ) == 64
217224 assert all (c in "0123456789abcdef" for c in fp )
225+
226+
227+ def find_icon_color (card : dict [str , Any ]) -> str | None :
228+ """Find the icon color in the adaptive card body."""
229+ for item in card .get ("body" , []):
230+ if item .get ("type" ) == "ColumnSet" :
231+ for col in item .get ("columns" , []):
232+ for inner in col .get ("items" , []):
233+ if inner .get ("type" ) == "Icon" :
234+ color = inner .get ("color" )
235+ return str (color ) if color is not None else None
236+ return None
237+
238+
239+ class TestIconColor :
240+ """Tests for icon color based on MR state and unresolved threads."""
241+
242+ def test_default_icon_color_no_discussion_stats (self ):
243+ """Icon should be accent when no discussion stats."""
244+ mri = make_mri ()
245+ result = render (mri )
246+ assert find_icon_color (result ) == "accent"
247+
248+ def test_default_icon_color_no_unresolved_threads (self ):
249+ """Icon should be accent when all threads resolved."""
250+ stats = DiscussionStats (threads_total = 3 , threads_resolved = 3 , threads_unresolved = 0 )
251+ mri = make_mri (discussion_stats = stats )
252+ result = render (mri )
253+ assert find_icon_color (result ) == "accent"
254+
255+ def test_warning_icon_color_with_unresolved_threads (self ):
256+ """Icon should be warning (orange) when there are unresolved threads."""
257+ stats = DiscussionStats (threads_total = 3 , threads_resolved = 1 , threads_unresolved = 2 )
258+ mri = make_mri (discussion_stats = stats )
259+ result = render (mri )
260+ assert find_icon_color (result ) == "warning"
261+
262+ def test_closed_mr_keeps_attention_regardless_of_threads (self ):
263+ """Closed MR should keep attention color, not be overridden by thread status."""
264+ stats = DiscussionStats (threads_total = 3 , threads_resolved = 1 , threads_unresolved = 2 )
265+ mri = make_mri (action = "close" , discussion_stats = stats )
266+ result = render (mri )
267+ assert find_icon_color (result ) == "attention"
268+
269+ def test_merged_mr_keeps_good_regardless_of_threads (self ):
270+ """Merged MR should keep good color, not be overridden by thread status."""
271+ stats = DiscussionStats (threads_total = 3 , threads_resolved = 1 , threads_unresolved = 2 )
272+ mri = make_mri (action = "merge" , discussion_stats = stats )
273+ result = render (mri )
274+ assert find_icon_color (result ) == "good"
275+
276+ def test_draft_mr_with_unresolved_threads_shows_warning (self ):
277+ """Draft MR with unresolved threads should show warning color."""
278+ stats = DiscussionStats (threads_total = 2 , threads_resolved = 0 , threads_unresolved = 2 )
279+ mri = make_mri (draft = True , discussion_stats = stats )
280+ result = render (mri )
281+ assert find_icon_color (result ) == "warning"
282+
283+ def test_draft_mr_without_unresolved_threads_shows_default (self ):
284+ """Draft MR without unresolved threads should show default color."""
285+ stats = DiscussionStats (threads_total = 2 , threads_resolved = 2 , threads_unresolved = 0 )
286+ mri = make_mri (draft = True , discussion_stats = stats )
287+ result = render (mri )
288+ assert find_icon_color (result ) == "default"
0 commit comments