
    i>                    
   d Z ddlmZ ddlZddlmc mZ ddl	Z	ddl
Z
ddlZddlmZmZmZ ddlmZmZmZmZ ddlZddlmZ ddlmZmZ dZddd	Zdd
Zd ZddZ G d d      Z G d d      Z  G d d      Z! G d d      Z"y)u  
Story 11.03 — Cron Scheduler Integration Tests
===============================================
ALL external calls are fully mocked:
  - ingest_platform   (core.kb.cron.ingest_platform)
  - list_platforms    (core.kb.cron.list_platforms)
  - get_platform      (core.kb.cron.get_platform)
  - get_ingestion_history  (core.kb.cron.get_ingestion_history)
  - get_connection    (core.kb.cron.get_connection)

No real network traffic, API keys, or database connections required.

Test summary (16 tests):

Class TestNightlyIngestion (7 tests — black-box):
  BB  test_runs_all_platforms           — 3 platforms → all 3 checked
  BB  test_skips_recent_ingestion       — 1h ago, refresh=168h → skipped
  BB  test_ingests_stale_platform       — 200h ago, refresh=168h → ingested
  BB  test_ingests_never_ingested       — no history → ingested
  BB  test_error_in_one_continues       — platform A errors → B still runs
  BB  test_dry_run_skips_ingestion      — dry_run=True → checked but not ingested
  BB  test_stats_returned               — result has all expected keys

Class TestShouldRefresh (4 tests — white-box):
  WB  test_no_history_returns_true      — empty history → True
  WB  test_recent_returns_false         — 1h ago, refresh=168h → False
  WB  test_stale_returns_true           — 200h ago, refresh=168h → True
  WB  test_exact_boundary               — exactly 168h → True (>= comparison)

Class TestCli (2 tests — black-box):
  BB  test_cli_runs                     — __main__ block executes without error
  BB  test_dry_run_flag                 — --dry-run flag parsed and passed through

Class TestCronScript (3 tests — static analysis):
  ST  test_script_exists                — scripts/setup_kb_cron.sh exists
  ST  test_script_executable            — file has correct shebang
  ST  test_script_idempotent_logic      — grep idempotency check is in script
    )annotationsN)datetimetimezone	timedelta)	AsyncMock	MagicMockpatchcall)PlatformConfig)nightly_ingestion_should_refreshz./mnt/e/genesis-system/scripts/setup_kb_cron.shc                D    t        | | j                         d|  d|      S )z,Create a minimal PlatformConfig for testing.zhttps://docs.z.com)namedisplay_namedocs_base_urlrefresh_hours)r   title)r   r   s     ;/mnt/e/genesis-system/tests/kb/test_m11_cron_integration.py_make_configr   @   s*    ZZ\%dV40#	     c                n    t        j                  t        j                        t	        |       z
  }|ddgS )z>Return a fake ingestion history row with started_at hours_ago.)hours	completed)
started_atstatus)r   nowr   utcr   )	hours_agor   s     r   _make_historyr   J   s,    hll+ii.HHJ%=>>r   c                    t        j                         }	 |j                  |       |j                          S # |j                          w xY w)zRun a coroutine synchronously.)asyncionew_event_looprun_until_completeclose)coroloops     r   _runr'   P   s6    !!#D&&t,



s	   7 A	c                    | dddddddS )N
   2   r   r         ?)platformpages_fetchedchunks_createdvectors_upsertederrorsr   duration_seconds )r,   s    r   _make_fake_statsr3   Y   s!     r   c                  X   e Zd ZdZ ed       ed       ede       ed       ed      d                                    Z ed       ed       ede       ed       ed      d	                                    Z ed       ed       ede       ed       ed      d
                                    Z ed       ed       ede       ed       ed      d                                    Z	 ed       ed       ede       ed       ed      d                                    Z
 ed       ed       ede       ed       ed      d                                    Z ed       ed       ede       ed       ed      d                                    Zy)TestNightlyIngestionz(Black-box tests for nightly_ingestion().zcore.kb.cron.get_connectionzcore.kb.cron._should_refreshzcore.kb.cron.ingest_platform)new_callablezcore.kb.cron.get_platformzcore.kb.cron.list_platformsc                ,   g d|_         d |_        d|_         d |_        t               |j                   _        t	        t                     }|d   }d}||k(  }	|	slt        j                  d|	fd||f      t        j                  |      t        j                  |      d	z  }
d
d|
iz  }t        t        j                  |            dx}x}	}|d   }d}||k(  }	|	slt        j                  d|	fd||f      t        j                  |      t        j                  |      d	z  }
d
d|
iz  }t        t        j                  |            dx}x}	}|d   }d}||k(  }	|	slt        j                  d|	fd||f      t        j                  |      t        j                  |      d	z  }
d
d|
iz  }t        t        j                  |            dx}x}	}|d   }d}||k(  }	|	slt        j                  d|	fd||f      t        j                  |      t        j                  |      d	z  }
d
d|
iz  }t        t        j                  |            dx}x}	}|j                  }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}y)u;   3 platforms → all 3 are checked and ingested (all stale).)hubspottelnyxstripec                    t        |       S Nr   ns    r   <lambda>z>TestNightlyIngestion.test_runs_all_platforms.<locals>.<lambda>v   
    a r   Tc                    t        |       S r<   )r3   )r?   kws     r   r@   z>TestNightlyIngestion.test_runs_all_platforms.<locals>.<lambda>x   s    2B12E r   platforms_checked   ==z%(py1)s == %(py4)spy1py4assert %(py6)spy6Nplatforms_ingestedplatforms_skippedr   r0   )z2%(py2)s
{%(py2)s = %(py0)s.call_count
} == %(py5)smock_ingestpy0py2py5assert %(py7)spy7)return_valueside_effectr   r$   r'   r   
@pytest_ar_call_reprcompare	_safereprAssertionError_format_explanation
call_count@py_builtinslocals_should_repr_global_name)self	mock_listmock_getrP   mock_should	mock_connresult@py_assert0@py_assert3@py_assert2@py_format5@py_format7@py_assert1@py_assert4@py_format6@py_format8s                   r   test_runs_all_platformsz,TestNightlyIngestion.test_runs_all_platformsl   s     "A	8#' "E'0{	$')*)*/a/*a////*a///*///a///////*+0q0+q0000+q000+000q0000000)*/a/*a////*a///*///a///////h$1$1$$$$1$$$$$$1$$$$$$$%%**%****%******{***{***%**********r   c                   dg|_         t        dd      |_         d|_         t               |j                   _        t	        t                     }|d   }d}||k(  }	|	slt        j                  d|	fd||f      t        j                  |      t        j                  |      d	z  }
d
d|
iz  }t        t        j                  |            dx}x}	}|d   }d}||k(  }	|	slt        j                  d|	fd||f      t        j                  |      t        j                  |      d	z  }
d
d|
iz  }t        t        j                  |            dx}x}	}|d   d   }d}||k(  }	|	slt        j                  d|	fd||f      t        j                  |      t        j                  |      d	z  }
d
d|
iz  }t        t        j                  |            dx}x}	}|j                          y)u3   Platform ingested 1h ago, refresh=168h → skipped.r8      r   FrO      rF   rH   rI   rL   rM   NrN   r   platform_resultsskipped)rW   r   r   r$   r'   r   rY   rZ   r[   r\   r]   assert_not_calledrb   rc   rd   rP   re   rf   rg   rh   ri   rj   rk   rl   s               r   test_skips_recent_ingestionz0TestNightlyIngestion.test_skips_recent_ingestion   sM    #,	 ,Yc J#( '0{	$')*)*/a/*a////*a///*///a///////*+0q0+q0000+q000+000q0000000())4A	A4	AAAA4	AAA4AAA	AAAAAAA%%'r   c                   dg|_         t        dd      |_         d|_         t        d      |_         t               |j                   _        t        t                     }|d   }d}||k(  }	|	slt        j                  d|	fd||f      t        j                  |      t        j                  |      d	z  }
d
d|
iz  }t        t        j                  |            dx}x}	}|d   }d}||k(  }	|	slt        j                  d|	fd||f      t        j                  |      t        j                  |      d	z  }
d
d|
iz  }t        t        j                  |            dx}x}	}|d   d   d   }d}||k(  }	|	slt        j                  d|	fd||f      t        j                  |      t        j                  |      d	z  }
d
d|
iz  }t        t        j                  |            dx}x}	}y)u6   Platform last run 200h ago, refresh=168h → ingested.r9   rs   rt   TrN   ru   rF   rH   rI   rL   rM   NrO   r   rv   r,   rW   r   r3   r   r$   r'   r   rY   rZ   r[   r\   r]   ry   s               r   test_ingests_stale_platformz0TestNightlyIngestion.test_ingests_stale_platform   sS    #+	 ,XS I#' #3H#= '0{	$')**+0q0+q0000+q000+000q0000000)*/a/*a////*a///*///a///////()(3J?K8K?8KKKK?8KKK?KKK8KKKKKKKr   c                   dg|_         t        d      |_         d|_         t        d      |_         t               |j                   _        t        t                     }|d   }d}||k(  }	|	slt        j                  d|	fd||f      t        j                  |      t        j                  |      dz  }
dd	|
iz  }t        t        j                  |            d
x}x}	}|d   }d}||k(  }	|	slt        j                  d|	fd||f      t        j                  |      t        j                  |      dz  }
dd	|
iz  }t        t        j                  |            d
x}x}	}y
)u>   Platform with no history → should_refresh=True → ingested.r:   TrN   ru   rF   rH   rI   rL   rM   Nr0   r   r|   ry   s               r   test_ingests_never_ingestedz0TestNightlyIngestion.test_ingests_never_ingested   s     #+	 ,X 6#' #3H#= '0{	$')**+0q0+q0000+q000+000q0000000h$1$1$$$$1$$$$$$1$$$$$$$r   c                   ddg|_         d |_        d|_         d }||_        t               |j                   _        t	        t                     }|d   }d}	||	k(  }
|
slt        j                  d|
fd	||	f      t        j                  |      t        j                  |	      d
z  }dd|iz  }t        t        j                  |            dx}x}
}	|d   }d}	||	k(  }
|
slt        j                  d|
fd	||	f      t        j                  |      t        j                  |	      d
z  }dd|iz  }t        t        j                  |            dx}x}
}	d}|d   d   }	||	v }
|
slt        j                  d|
fd||	f      t        j                  |      t        j                  |	      d
z  }dd|iz  }t        t        j                  |            dx}x}
}	|d   d   d   }d}	||	k(  }
|
slt        j                  d|
fd	||	f      t        j                  |      t        j                  |	      d
z  }dd|iz  }t        t        j                  |            dx}x}
}	y)z,If platform A raises, platform B still runs.r8   r9   c                    t        |       S r<   r=   r>   s    r   r@   zBTestNightlyIngestion.test_error_in_one_continues.<locals>.<lambda>   rA   r   Tc                @   K   | dk(  rt        d      t        |       S w)Nr8   znetwork failure)RuntimeErrorr3   )r   rC   s     r   _side_effectzFTestNightlyIngestion.test_error_in_one_continues.<locals>._side_effect   s%     y "#455#D))s   r0   ru   rF   rH   rI   rL   rM   NrN   errorrv   in)z%(py1)s in %(py4)sr,   )rW   rX   r   r$   r'   r   rY   rZ   r[   r\   r]   )rb   rc   rd   rP   re   rf   r   rg   rh   ri   rj   rk   rl   s                r   test_error_in_one_continuesz0TestNightlyIngestion.test_error_in_one_continues   s    #,X!6	8#' 	*
 #/'0{	$')*h$1$1$$$$1$$$$$$1$$$$$$$*+0q0+q0000+q000+000q0000000?&!34Y??w?????w????w???????????()(3J?K8K?8KKKK?8KKK?KKK8KKKKKKKr   c                   ddg|_         d |_        d|_         t               |j                   _        t	        t        d            }|j                          |d   }d}||k(  }	|	slt        j                  d|	fd	||f      t        j                  |      t        j                  |      d
z  }
dd|
iz  }t        t        j                  |            dx}x}	}|d   }d}||k(  }	|	slt        j                  d|	fd	||f      t        j                  |      t        j                  |      d
z  }
dd|
iz  }t        t        j                  |            dx}x}	}|d   d   }d}||k(  }	|	slt        j                  d|	fd	||f      t        j                  |      t        j                  |      d
z  }
dd|
iz  }t        t        j                  |            dx}x}	}|d   d   }d}||k(  }	|	slt        j                  d|	fd	||f      t        j                  |      t        j                  |      d
z  }
dd|
iz  }t        t        j                  |            dx}x}	}y)uD   dry_run=True → platforms checked but ingest_platform never called.r8   r:   c                    t        |       S r<   r=   r>   s    r   r@   zCTestNightlyIngestion.test_dry_run_skips_ingestion.<locals>.<lambda>   rA   r   T)dry_runrN      rF   rH   rI   rL   rM   NrO   r   rv   would_ingest)rW   rX   r   r$   r'   r   rx   rY   rZ   r[   r\   r]   ry   s               r   test_dry_run_skips_ingestionz1TestNightlyIngestion.test_dry_run_skips_ingestion   s    #,X!6	8#' '0{	$'56%%'*+0q0+q0000+q000+000q0000000)*/a/*a////*a///*///a///////())4FF4FFFF4FFF4FFFFFFFFFF()(3E~E3~EEEE3~EEE3EEE~EEEEEEEr   c           	        g |_         t               |j                   _        t        t	                     }h d}|j
                  }|j                  }	 |	       }
 ||
      }|sddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |	      t        j                  |
      t        j                  |      dz  }t        t        j                  |            dx}x}	x}
}|d   }t        |t              }	|	sdd	t        j                         v st        j                  t              rt        j                  t              nd	t        j                  |      d
t        j                         v st        j                  t              rt        j                  t              nd
t        j                  |	      dz  }t        t        j                  |            dx}}	|d   }t        |t               }	|	sdd	t        j                         v st        j                  t              rt        j                  t              nd	t        j                  |      dt        j                         v st        j                  t               rt        j                  t               ndt        j                  |	      dz  }t        t        j                  |            dx}}	|d   }t        |t"              }	|	sdd	t        j                         v st        j                  t              rt        j                  t              nd	t        j                  |      dt        j                         v st        j                  t"              rt        j                  t"              ndt        j                  |	      dz  }t        t        j                  |            dx}}	y)z3Result dict always has all required top-level keys.>   r0   run_atrv   rD   rO   rN   total_duration_secondsz{assert %(py9)s
{%(py9)s = %(py2)s
{%(py2)s = %(py0)s.issubset
}(%(py7)s
{%(py7)s = %(py5)s
{%(py5)s = %(py3)s.keys
}()
})
}required_keysrg   )rR   rS   py3rT   rV   py9Nr   z5assert %(py5)s
{%(py5)s = %(py0)s(%(py2)s, %(py3)s)
}
isinstancestr)rR   rS   r   rT   r   floatrv   dict)rW   r   r$   r'   r   issubsetkeysr_   r`   rY   ra   r[   r\   r]   r   r   r   r   )rb   rc   rd   rP   re   rf   rg   r   rm   rn   @py_assert6@py_assert8@py_format10ro   s                 r   test_stats_returnedz(TestNightlyIngestion.test_stats_returned   sZ    "$	'0{	$')*
 %%4fkk4km4%m44444444}444}444%444444f444f444k444m4444444444 *0z*C00000000z000z000*000000C000C0000000000 !9:Bz:EBBBBBBBBzBBBzBBB:BBBBBBEBBBEBBBBBBBBBB !34;z4d;;;;;;;;z;;;z;;;4;;;;;;d;;;d;;;;;;;;;;r   N)__name__
__module____qualname____doc__r	   r   rq   rz   r}   r   r   r   r   r2   r   r   r5   r5   i   s\   2
()
)*
)	B
&'
()+ * ( C + *
+$ ()
)*
)	B
&'
()( * ( C + *
(  ()
)*
)	B
&'
()L * ( C + *
L  ()
)*
)	B
&'
()% * ( C + *
% ()
)*
)	B
&'
()L * ( C + *
L. ()
)*
)	B
&'
()F * ( C + *
F" ()
)*
)	B
&'
()< * ( C + *
<r   r5   c                      e Zd ZdZ ed      d        Z ed      d        Z ed      d        Z ed      d        Zy)TestShouldRefreshz&White-box tests for _should_refresh().z"core.kb.cron.get_ingestion_historyc           	        g |_         t               }d}d}t        |||      }d}||u }|st        j                  d|fd||f      dt        j                         v st        j                  t              rt        j                  t              ndt        j                  |      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }d	d
|iz  }	t        t        j                  |	            dx}x}x}x}}y)u=   Empty history → platform never ingested → should refresh.r8   rs   TiszC%(py7)s
{%(py7)s = %(py0)s(%(py2)s, %(py4)s, %(py5)s)
} is %(py10)sr   connrR   rS   rK   rT   rV   py10assert %(py12)spy12N)rW   r   r   rY   rZ   r_   r`   ra   r[   r\   r]   
rb   mock_historyr   rm   ri   r   @py_assert9r   @py_format11@py_format13s
             r   test_no_history_returns_truez.TestShouldRefresh.test_no_history_returns_true  s     %'!{(<#<y#t4<<4<<<<4<<<<<<<<<<<<y<<<#<<<<<<t<<<t<<<4<<<<<<<<<<<r   c           	        t        d      |_        t               }d}d}t        |||      }d}||u }|st	        j
                  d|fd||f      dt        j                         v st	        j                  t              rt	        j                  t              ndt	        j                  |      t	        j                  |      d	t        j                         v st	        j                  |      rt	        j                  |      nd	t	        j                  |      t	        j                  |      d
z  }dd|iz  }	t        t	        j                  |	            dx}x}x}x}}y)u8   Ingested 1h ago, refresh=168h → still fresh → False.r+   r   r8   rs   Fr   r   r   r   r   r   r   Nr   rW   r   r   rY   rZ   r_   r`   ra   r[   r\   r]   r   s
             r   test_recent_returns_falsez+TestShouldRefresh.test_recent_returns_false  s     %2C$@!{(=#=y#t4==4====4============y===#======t===t===4===========r   c           	        t        d      |_        t               }d}d}t        |||      }d}||u }|st	        j
                  d|fd||f      dt        j                         v st	        j                  t              rt	        j                  t              ndt	        j                  |      t	        j                  |      d	t        j                         v st	        j                  |      rt	        j                  |      nd	t	        j                  |      t	        j                  |      d
z  }dd|iz  }	t        t	        j                  |	            dx}x}x}x}}y)u3   Ingested 200h ago, refresh=168h → stale → True.g      i@r   r8   rs   Tr   r   r   r   r   r   r   Nr   r   s
             r   test_stale_returns_truez)TestShouldRefresh.test_stale_returns_true%       %2E$B!{(<#<y#t4<<4<<<<4<<<<<<<<<<<<y<<<#<<<<<<t<<<t<<<4<<<<<<<<<<<r   c           	        t        d      |_        t               }d}d}t        |||      }d}||u }|st	        j
                  d|fd||f      dt        j                         v st	        j                  t              rt	        j                  t              ndt	        j                  |      t	        j                  |      d	t        j                         v st	        j                  |      rt	        j                  |      nd	t	        j                  |      t	        j                  |      d
z  }dd|iz  }	t        t	        j                  |	            dx}x}x}x}}y)uF   Exactly 168h elapsed, refresh=168h → should refresh (>= comparison).g      e@r   r8   rs   Tr   r   r   r   r   r   r   Nr   r   s
             r   test_exact_boundaryz%TestShouldRefresh.test_exact_boundary,  r   r   N)	r   r   r   r   r	   r   r   r   r   r2   r   r   r   r     sk    0
/0= 1= /0> 1> /0= 1= /0= 1=r   r   c                      e Zd ZdZd Zd Zy)TestCliz7Tests for the CLI argument parser and argument routing.c                   ddl m}  |       }|j                  g       }|j                  }d}||u }|st	        j
                  d|fd||f      dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      t	        j                  |      dz  }dd	|iz  }t        t	        j                  |            d
x}x}}y
)u8   _build_parser() default (no flags) → dry_run is False.r   _build_parserFr   z/%(py2)s
{%(py2)s = %(py0)s.dry_run
} is %(py5)sargsrQ   rU   rV   Ncore.kb.cronr   
parse_argsr   rY   rZ   r_   r`   ra   r[   r\   r]   	rb   r   parserr   rm   rn   ri   ro   rp   s	            r   test_cli_runszTestCli.test_cli_runs;  s    .  $||$u$|u$$$$|u$$$$$$t$$$t$$$|$$$u$$$$$$$r   c                   ddl m}  |       }|j                  dg      }|j                  }d}||u }|st	        j
                  d|fd||f      dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      t	        j                  |      dz  }d	d
|iz  }t        t	        j                  |            dx}x}}y)u/   --dry-run flag parsed → args.dry_run is True.r   r   z	--dry-runTr   r   r   rQ   rU   rV   Nr   r   s	            r   test_dry_run_flagzTestCli.test_dry_run_flagB  s    .  +/||#t#|t####|t######t###t###|###t#######r   N)r   r   r   r   r   r   r2   r   r   r   r   8  s    A%$r   r   c                  "    e Zd ZdZd Zd Zd Zy)TestCronScriptz3Static analysis tests for scripts/setup_kb_cron.sh.c                   t         j                  }|j                  } |t              }|st	        j
                  dt               dz   dt        j                         v st	        j                  t               rt	        j                  t               ndt	        j                  |      t	        j                  |      dt        j                         v st	        j                  t              rt	        j                  t              ndt	        j                  |      dz  }t        t	        j                  |            dx}x}}y)z$scripts/setup_kb_cron.sh must exist.zSetup script not found at zd
>assert %(py7)s
{%(py7)s = %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.path
}.isfile
}(%(py5)s)
}osSCRIPT_PATH)rR   rS   rK   rT   rV   N)r   pathisfiler   rY   _format_assertmsgr_   r`   ra   r[   r\   r]   )rb   rm   ri   r   rp   s        r   test_script_existsz!TestCronScript.test_script_existsQ  s   ww 	
w~~ 	
~k* 	
* 	
  )6	
 	
	6	
 	
   	
 	
 		  	
 	
 		  	
 	
 		  	
 	
	6	
 	
  * 	
 	
 		 * 	
 	
 		 + 	
 	
 	
 	
 	
 	
r   c                   t        t        dd      5 }|j                         j                         }ddd       j                  }d} ||      }|st        j                  d|      dz   dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      d	z  }t        t        j                  |            dx}x}}d
}||v }|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }	t        j                  d|      dz   d|	iz  }
t        t        j                  |
            dx}}y# 1 sw Y   xY w)z)File must start with a bash shebang line.rutf-8encodingNz#!/zExpected shebang line, got: zN
>assert %(py6)s
{%(py6)s = %(py2)s
{%(py2)s = %(py0)s.startswith
}(%(py4)s)
}
first_line)rR   rS   rK   rM   bashr   z%(py1)s in %(py3)srJ   r   zExpected bash shebang, got: 
>assert %(py5)srT   )openr   readlinestrip
startswithrY   r   r_   r`   ra   r[   r\   r]   rZ   )rb   fhr   rm   ri   @py_assert5rl   rh   rj   @py_format4ro   s              r   test_script_executablez%TestCronScript.test_script_executableW  s   +sW5 	/,,.J	/$$ 	
U 	
$U+ 	
+ 	
  +:.9	
 	
	6	
 	
   	
 	
 		  	
 	
 		 % 	
 	
 		 &+ 	
 	
 		 , 	
 	
 	
 	
 	
  	
v# 	
 	
v 	
 	
 		  	
 	
	6	
 	
  $ 	
 	
 		 $ 	
 	
  +:.9	
 	
 	
 	
 	
	/ 	/s   GGc                   t        t        dd      5 }|j                         }ddd       d}|v }|st        j                  d|fd||f      t        j
                  |      dt        j                         v st        j                  |      rt        j
                  |      ndd	z  }t        j                  d
      dz   d|iz  }t        t        j                  |            dx}}d}||v }|st        j                  d|fd||f      t        j
                  |      dt        j                         v st        j                  |      rt        j
                  |      ndd	z  }t        j                  d      dz   d|iz  }t        t        j                  |            dx}}d}||v }|st        j                  d|fd||f      t        j
                  |      dt        j                         v st        j                  |      rt        j
                  |      ndd	z  }t        j                  d      dz   d|iz  }t        t        j                  |            dx}}y# 1 sw Y   <xY w)zAScript must contain grep check to prevent duplicate cron entries.r   r   r   Ngrepr   r   contentr   z*Script must use grep for idempotency checkr   rT   zcore.kb.cronz"Script must reference core.kb.cronzalready existsz<Script must print 'already exists' when entry is a duplicate)r   r   readrY   rZ   r[   r_   r`   ra   r   r\   r]   )rb   r   r   rh   rj   r   ro   s          r   test_script_idempotent_logicz+TestCronScript.test_script_idempotent_logicb  s   +sW5 	 ggiG	 Nv NNNvNNNvNNNNNNNNNNNNN"NNNNNNNN~(NNN~NNN~NNNNNNNNNNNNN*NNNNNNN 	
7* 	
 	
7 	
 	
 		   	
 	
	6	
 	
  $+ 	
 	
 		 $+ 	
 	
  K	
 	
 	
 	
 	
		  	 s   II(N)r   r   r   r   r   r   r   r2   r   r   r   r   N  s    =
	

r   r   )rs   )r   r   r   intreturnr   )r   r   r   z
list[dict])r,   r   r   r   )#r   
__future__r   builtinsr_   _pytest.assertion.rewrite	assertionrewriterY   r!   jsonr   r   r   r   unittest.mockr   r   r	   r
   pytestcore.kb.contractsr   r   r   r   r   r   r   r'   r3   r5   r   r   r   r2   r   r   <module>r      s{   %N #     	 2 2 ; ;  , < ??	 d< d<V= =H$ $,
 
r   