Skip to content
Draft
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
Empty file modified .github/hooks/post-edit-invalidate.sh
100644 → 100755
Empty file.
Empty file modified .github/hooks/pre-amend-block.sh
100644 → 100755
Empty file.
Empty file modified .github/hooks/pre-commit-block.sh
100644 → 100755
Empty file.
Empty file modified .github/hooks/pre-force-push-block.sh
100644 → 100755
Empty file.
Empty file modified .github/hooks/pre-layer-import.sh
100644 → 100755
Empty file.
Empty file modified .github/hooks/pre-layer-mock.sh
100644 → 100755
Empty file.
Empty file modified .github/hooks/pre-push-block.sh
100644 → 100755
Empty file.
Empty file modified .github/hooks/pre-reexport-block.sh
100644 → 100755
Empty file.
195 changes: 6 additions & 189 deletions schedule.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,38 +19,12 @@
"days": ["mon", "tue", "wed", "thu", "fri", "sat", "sun"],
"time": "08:00",
"label": "Morning"
},
{
"days": ["mon", "tue", "wed", "thu", "fri", "sat", "sun"],
"time": "12:00",
"label": "Lunch"
},
{
"days": ["mon", "tue", "wed", "thu", "fri", "sat", "sun"],
"time": "15:00",
"label": "Afternoon"
}
],
"avoidDays": []
},
"medium-clip": {
"slots": [
{
"days": ["mon", "tue", "wed", "thu", "fri", "sat", "sun"],
"time": "10:00",
"label": "Morning deep dive"
},
{
"days": ["mon", "tue", "wed", "thu", "fri", "sat", "sun"],
"time": "14:00",
"label": "Lunch deep dive"
},
{
"days": ["mon", "tue", "wed", "thu", "fri", "sat", "sun"],
"time": "17:00",
"label": "Afternoon deep dive"
}
],
"slots": [],
"avoidDays": []
}
}
Expand All @@ -74,19 +48,6 @@
"time": "07:30",
"label": "Morning scroll"
},
{
"days": [
"mon",
"tue",
"wed",
"thu",
"fri",
"sat",
"sun"
],
"time": "12:30",
"label": "Lunch break"
},
{
"days": [
"mon",
Expand All @@ -104,21 +65,7 @@
"avoidDays": []
},
"medium-clip": {
"slots": [
{
"days": [
"mon",
"tue",
"wed",
"thu",
"fri",
"sat",
"sun"
],
"time": "15:00",
"label": "Afternoon engagement"
}
],
"slots": [],
"avoidDays": []
}
}
Expand All @@ -141,52 +88,12 @@
],
"time": "08:00",
"label": "Morning upload"
},
{
"days": [
"mon",
"tue",
"wed",
"thu",
"fri",
"sat",
"sun"
],
"time": "13:00",
"label": "Afternoon upload"
},
{
"days": [
"mon",
"tue",
"wed",
"thu",
"fri",
"sat",
"sun"
],
"time": "18:00",
"label": "Evening upload"
}
],
"avoidDays": []
},
"medium-clip": {
"slots": [
{
"days": [
"mon",
"tue",
"wed",
"thu",
"fri",
"sat",
"sun"
],
"time": "16:00",
"label": "Late afternoon feature"
}
],
"slots": [],
"avoidDays": []
},
"video": {
Expand Down Expand Up @@ -221,66 +128,16 @@
],
"time": "08:30",
"label": "Morning reel"
},
{
"days": [
"mon",
"tue",
"wed",
"thu",
"fri",
"sat",
"sun"
],
"time": "12:00",
"label": "Lunch reel"
},
{
"days": [
"mon",
"tue",
"wed",
"thu",
"fri",
"sat",
"sun"
],
"time": "15:30",
"label": "Afternoon reel"
}
],
"avoidDays": []
},
"medium-clip": {
"slots": [
{
"days": ["mon", "tue", "wed", "thu", "fri", "sat", "sun"],
"time": "10:30",
"label": "Morning feature"
},
{
"days": ["mon", "tue", "wed", "thu", "fri", "sat", "sun"],
"time": "14:00",
"label": "Lunch feature"
},
{
"days": ["mon", "tue", "wed", "thu", "fri", "sat", "sun"],
"time": "17:30",
"label": "Afternoon feature"
}
],
"slots": [],
"avoidDays": []
},
"video": {
"slots": [
{
"days": [
"sat"
],
"time": "14:00",
"label": "Weekend showcase"
}
],
"slots": [],
"avoidDays": []
}
}
Expand All @@ -304,19 +161,6 @@
"time": "07:00",
"label": "Early bird"
},
{
"days": [
"mon",
"tue",
"wed",
"thu",
"fri",
"sat",
"sun"
],
"time": "10:30",
"label": "Mid-morning"
},
{
"days": [
"mon",
Expand All @@ -329,39 +173,12 @@
],
"time": "14:00",
"label": "Afternoon"
},
{
"days": [
"mon",
"tue",
"wed",
"thu",
"fri",
"sat",
"sun"
],
"time": "20:30",
"label": "Night owl"
}
],
"avoidDays": []
},
"medium-clip": {
"slots": [
{
"days": [
"mon",
"tue",
"wed",
"thu",
"fri",
"sat",
"sun"
],
"time": "17:00",
"label": "End of day"
}
],
"slots": [],
"avoidDays": []
}
}
Expand Down
23 changes: 14 additions & 9 deletions src/__tests__/e2e/scheduleConfigValidation.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { describe, test, expect, beforeAll } from 'vitest'
import { Platform } from '../../L0-pure/types/index.js'
import { loadScheduleConfig, getPlatformSchedule, clearScheduleCache } from '../../L3-services/scheduler/scheduleConfig.js'
import type { ClipType } from '../../L3-services/socialPosting/platformContentStrategy.js'

/**
* Config validation: ensures every platform that the pipeline generates posts for
Expand All @@ -12,8 +11,6 @@ import type { ClipType } from '../../L3-services/socialPosting/platformContentSt
* to silently fail with "No available slot."
*/

const ALL_CLIP_TYPES: ClipType[] = ['video', 'short', 'medium-clip']

const PLATFORM_SCHEDULE_KEYS: Record<Platform, string> = {
[Platform.YouTube]: 'youtube',
[Platform.LinkedIn]: 'linkedin',
Expand All @@ -22,20 +19,28 @@ const PLATFORM_SCHEDULE_KEYS: Record<Platform, string> = {
[Platform.X]: 'x',
}

describe('schedule.json ↔ content strategy consistency', () => {
const EXPECTED_SLOT_COUNTS: Record<string, Partial<Record<'video' | 'short' | 'medium-clip', number>>> = {
youtube: { short: 1, 'medium-clip': 0, video: 1 },
linkedin: { short: 1, 'medium-clip': 0 },
tiktok: { short: 2, 'medium-clip': 0 },
instagram: { short: 1, 'medium-clip': 0, video: 0 },
x: { short: 2, 'medium-clip': 0 },
}

describe('schedule.json slot sizing', () => {
beforeAll(async () => {
clearScheduleCache()
await loadScheduleConfig()
})

for (const platform of Object.values(Platform)) {
for (const clipType of ALL_CLIP_TYPES) {
const scheduleKey = PLATFORM_SCHEDULE_KEYS[platform]

test(`${scheduleKey}/${clipType} has usable schedule slots (direct or fallback)`, () => {
const scheduleKey = PLATFORM_SCHEDULE_KEYS[platform]
const clipTypes = EXPECTED_SLOT_COUNTS[scheduleKey]
for (const [clipType, expectedSlotCount] of Object.entries(clipTypes)) {
test(`${scheduleKey}/${clipType} has ${expectedSlotCount} configured slots`, () => {
const schedule = getPlatformSchedule(scheduleKey, clipType)
expect(schedule, `No schedule config for ${scheduleKey}/${clipType}`).not.toBeNull()
expect(schedule!.slots.length, `${scheduleKey}/${clipType} resolved to 0 slots — posts cannot be approved`).toBeGreaterThan(0)
expect(schedule!.slots.length).toBe(expectedSlotCount)
})
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/__tests__/integration/L3/scheduler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ describe('L3 Integration: scheduler calendar with no Late API', () => {

it('findNextSlot accepts SlotOptions with ideaIds and publishBy', async () => {
// findNextSlot catches Late API errors internally via fetchScheduledPostsSafe
const slot = await findNextSlot('linkedin', 'medium-clip', {
const slot = await findNextSlot('linkedin', 'short', {
ideaIds: ['idea-123'],
publishBy: '2099-12-31T23:59:59Z',
})
Expand All @@ -169,11 +169,11 @@ describe('L3 Integration: scheduler calendar with no Late API', () => {
})

it('findNextSlot without ideaIds behaves identically to no options', async () => {
const slotWithoutOptions = await findNextSlot('linkedin', 'medium-clip')
const slotWithoutOptions = await findNextSlot('linkedin', 'short')

clearScheduleCache()

const slotWithoutIdeaIds = await findNextSlot('linkedin', 'medium-clip', {})
const slotWithoutIdeaIds = await findNextSlot('linkedin', 'short', {})

expect(slotWithoutIdeaIds).toBe(slotWithoutOptions)
})
Expand Down
Loading