import { expect, tap } from '@push.rocks/tapbundle'; import * as qenv from '@push.rocks/qenv'; const testQenv = new qenv.Qenv('./', './.nogit/'); import * as ghost from '../ts/index.js'; let testGhostInstance: ghost.Ghost; let createdPost: ghost.Post; let createdMember: ghost.Member; tap.test('initialize Ghost instance', async () => { testGhostInstance = new ghost.Ghost({ baseUrl: 'http://localhost:2368', adminApiKey: await testQenv.getEnvVarOnDemand('ADMIN_APIKEY'), contentApiKey: await testQenv.getEnvVarOnDemand('CONTENT_APIKEY'), }); expect(testGhostInstance).toBeInstanceOf(ghost.Ghost); }); tap.test('should return dates as strings in posts', async () => { const posts = await testGhostInstance.getPosts({ limit: 1 }); if (posts.length > 0) { const post = posts[0]; expect(typeof post.postData.created_at).toEqual('string'); expect(typeof post.postData.updated_at).toEqual('string'); expect(typeof post.postData.published_at).toEqual('string'); console.log(`Post dates are strings: created_at=${post.postData.created_at}`); } }); tap.test('should have valid ISO 8601 date format in posts', async () => { const posts = await testGhostInstance.getPosts({ limit: 1 }); if (posts.length > 0) { const post = posts[0]; // Check if dates can be parsed const createdDate = new Date(post.postData.created_at); const updatedDate = new Date(post.postData.updated_at); const publishedDate = new Date(post.postData.published_at); expect(createdDate.toString()).not.toEqual('Invalid Date'); expect(updatedDate.toString()).not.toEqual('Invalid Date'); expect(publishedDate.toString()).not.toEqual('Invalid Date'); // Check if dates are valid timestamps expect(isNaN(createdDate.getTime())).toEqual(false); expect(isNaN(updatedDate.getTime())).toEqual(false); expect(isNaN(publishedDate.getTime())).toEqual(false); console.log(`Parsed dates: created=${createdDate.toISOString()}, updated=${updatedDate.toISOString()}, published=${publishedDate.toISOString()}`); } }); tap.test('should have ISO 8601 format with timezone offset', async () => { const posts = await testGhostInstance.getPosts({ limit: 1 }); if (posts.length > 0) { const post = posts[0]; // ISO 8601 with timezone: YYYY-MM-DDTHH:mm:ss.sss±HH:mm const iso8601WithTimezonePattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}[+-]\d{2}:\d{2}$/; expect(iso8601WithTimezonePattern.test(post.postData.created_at)).toEqual(true); expect(iso8601WithTimezonePattern.test(post.postData.updated_at)).toEqual(true); expect(iso8601WithTimezonePattern.test(post.postData.published_at)).toEqual(true); console.log(`Dates match ISO 8601 with timezone pattern`); } }); tap.test('should create published post and have published_at set', async () => { const timestamp = Date.now(); createdPost = await testGhostInstance.adminApi.posts.add({ title: `Date Test Post ${timestamp}`, html: '
Testing date handling
', status: 'published' }, { source: 'html' }); createdPost = new ghost.Post(testGhostInstance, createdPost); expect(createdPost).toBeInstanceOf(ghost.Post); expect(createdPost.postData.status).toEqual('published'); expect(createdPost.postData.published_at).toBeTruthy(); // Published date should be a valid date const publishedDate = new Date(createdPost.postData.published_at); expect(publishedDate.toString()).not.toEqual('Invalid Date'); console.log(`Created published post with published_at: ${createdPost.postData.published_at}`); }); tap.test('should preserve published_at when updating post', async () => { if (createdPost) { const originalPublishedAt = createdPost.postData.published_at; const originalPublishedDate = new Date(originalPublishedAt); await createdPost.update({ html: 'Updated content
' }); const updatedPublishedDate = new Date(createdPost.postData.published_at); // The published_at date should remain the same (within a second tolerance for time parsing) expect(Math.abs(updatedPublishedDate.getTime() - originalPublishedDate.getTime())).toBeLessThan(1000); console.log(`Published date preserved after update: original=${originalPublishedAt}, updated=${createdPost.postData.published_at}`); } }); tap.test('should have updated_at change when updating metadata fields', async () => { if (createdPost) { const originalUpdatedAt = new Date(createdPost.postData.updated_at); const originalTitle = createdPost.postData.title; // Wait a moment to ensure time difference await new Promise(resolve => setTimeout(resolve, 1000)); // Update a metadata field (not just HTML) to trigger updated_at change await createdPost.update({ title: `${originalTitle} - Modified` }); const newUpdatedAt = new Date(createdPost.postData.updated_at); // The updated_at should be newer when metadata fields are updated expect(newUpdatedAt.getTime()).toBeGreaterThan(originalUpdatedAt.getTime()); console.log(`updated_at changed: ${originalUpdatedAt.toISOString()} -> ${newUpdatedAt.toISOString()}`); } }); tap.test('should delete test post', async () => { if (createdPost) { await createdPost.delete(); console.log(`Deleted test post: ${createdPost.getId()}`); } }); tap.test('should return dates as strings in members', async () => { const members = await testGhostInstance.getMembers({ limit: 1 }); if (members.length > 0) { const member = members[0]; expect(typeof member.memberData.created_at).toEqual('string'); expect(typeof member.memberData.updated_at).toEqual('string'); console.log(`Member dates are strings: created_at=${member.memberData.created_at}`); } else { console.log('No members to test - skipping member date test'); } }); tap.test('should have valid date format in members', async () => { const members = await testGhostInstance.getMembers({ limit: 1 }); if (members.length > 0) { const member = members[0]; const createdDate = new Date(member.memberData.created_at); const updatedDate = new Date(member.memberData.updated_at); expect(createdDate.toString()).not.toEqual('Invalid Date'); expect(updatedDate.toString()).not.toEqual('Invalid Date'); console.log(`Member dates parsed: created=${createdDate.toISOString()}, updated=${updatedDate.toISOString()}`); } else { console.log('No members to test - skipping member date validation'); } }); tap.test('should create member and verify dates', async () => { const timestamp = Date.now(); createdMember = await testGhostInstance.createMember({ email: `datetest-${timestamp}@example.com`, name: `Date Test User ${timestamp}` }); expect(createdMember).toBeInstanceOf(ghost.Member); expect(typeof createdMember.memberData.created_at).toEqual('string'); expect(typeof createdMember.memberData.updated_at).toEqual('string'); const createdDate = new Date(createdMember.memberData.created_at); expect(createdDate.toString()).not.toEqual('Invalid Date'); console.log(`Created member with dates: created_at=${createdMember.memberData.created_at}`); }); tap.test('should have recent created_at date for new member', async () => { if (createdMember) { const createdDate = new Date(createdMember.memberData.created_at); const now = new Date(); // Should be created within the last minute const timeDiff = now.getTime() - createdDate.getTime(); expect(timeDiff).toBeLessThan(60000); // Less than 1 minute expect(timeDiff).toBeGreaterThanOrEqual(0); // Not in the future console.log(`Member created ${Math.round(timeDiff / 1000)} seconds ago`); } }); tap.test('should delete test member', async () => { if (createdMember) { await createdMember.delete(); console.log(`Deleted test member: ${createdMember.getId()}`); } }); export default tap.start();