@@ -170,6 +170,8 @@ test('computeCampaignStatus prioritises ended over upcoming', () => {
170170
171171test ( 'campaign repository attaches computed status to returned campaigns' , async ( ) => {
172172 const repository = await setupTestRepository ( ) ;
173+ const future = new Date ( Date . now ( ) + 86_400_000 ) . toISOString ( ) ;
174+ const past = new Date ( Date . now ( ) - 86_400_000 ) . toISOString ( ) ;
173175
174176 const upcoming = repository . create ( {
175177 name : 'Future Campaign' ,
@@ -290,3 +292,72 @@ test('list includeHidden option exposes hidden campaigns', async () => {
290292 assert . equal ( repository . list ( ) . length , 1 ) ;
291293 assert . equal ( repository . list ( { includeHidden : true } ) . length , 2 ) ;
292294} ) ;
295+
296+ // #458 — clone campaign functionality
297+ test ( 'clone creates a new campaign with copied metadata' , async ( ) => {
298+ const repository = await setupTestRepository ( ) ;
299+
300+ const original = repository . create ( {
301+ name : 'Weekly Challenge' ,
302+ description : 'Complete tasks to earn rewards' ,
303+ rewardPerAction : 50 ,
304+ active : true ,
305+ featured : true ,
306+ } ) ;
307+
308+ const cloned = repository . clone ( original . id ) ;
309+
310+ assert . ok ( cloned ) ;
311+ assert . notEqual ( cloned . id , original . id ) ;
312+ assert . equal ( cloned . name , `Copy of ${ original . name } ` ) ;
313+ assert . equal ( cloned . description , original . description ) ;
314+ assert . equal ( cloned . rewardPerAction , original . rewardPerAction ) ;
315+ assert . equal ( cloned . active , false ) ; // cloned campaigns are draft
316+ assert . equal ( cloned . startDate , null ) ; // dates not copied
317+ assert . equal ( cloned . endDate , null ) ;
318+ assert . equal ( cloned . clonedFrom , original . id ) ;
319+ } ) ;
320+
321+ test ( 'clone with overrides applies custom values' , async ( ) => {
322+ const repository = await setupTestRepository ( ) ;
323+
324+ const original = repository . create ( {
325+ name : 'Original Campaign' ,
326+ description : 'Original description' ,
327+ rewardPerAction : 100 ,
328+ } ) ;
329+
330+ const cloned = repository . clone ( original . id , {
331+ name : 'Custom Name' ,
332+ description : 'Custom description' ,
333+ } ) ;
334+
335+ assert . ok ( cloned ) ;
336+ assert . equal ( cloned . name , 'Custom Name' ) ;
337+ assert . equal ( cloned . description , 'Custom description' ) ;
338+ assert . equal ( cloned . rewardPerAction , original . rewardPerAction ) ; // not overridden
339+ assert . equal ( cloned . clonedFrom , original . id ) ;
340+ } ) ;
341+
342+ test ( 'clone returns undefined for non-existent campaign' , async ( ) => {
343+ const repository = await setupTestRepository ( ) ;
344+
345+ const cloned = repository . clone ( '99999' ) ;
346+ assert . equal ( cloned , undefined ) ;
347+ } ) ;
348+
349+ test ( 'clone generates unique slug for cloned campaign' , async ( ) => {
350+ const repository = await setupTestRepository ( ) ;
351+
352+ const original = repository . create ( {
353+ name : 'Test Campaign' ,
354+ slug : 'test-campaign' ,
355+ rewardPerAction : 10 ,
356+ } ) ;
357+
358+ const cloned = repository . clone ( original . id ) ;
359+
360+ assert . ok ( cloned ) ;
361+ assert . notEqual ( cloned . slug , original . slug ) ;
362+ assert . equal ( cloned . slug , 'copy-of-test-campaign' ) ;
363+ } ) ;
0 commit comments