fix: overwrite handling for posts, pages and media
This commit is contained in:
@@ -49,6 +49,12 @@ const insertedPosts: Array<{
|
||||
author?: string;
|
||||
}> = [];
|
||||
|
||||
// Track all database updates
|
||||
const updatedPosts: Array<{
|
||||
id: string;
|
||||
data: any;
|
||||
}> = [];
|
||||
|
||||
const insertedMedia: Array<{
|
||||
id: string;
|
||||
linkedPostIds: string[];
|
||||
@@ -63,7 +69,7 @@ const writtenFiles: Array<{
|
||||
content: string;
|
||||
}> = [];
|
||||
|
||||
// Mock database that tracks inserts
|
||||
// Mock database that tracks inserts and updates
|
||||
const mockDb = {
|
||||
insert: vi.fn().mockImplementation((table: any) => ({
|
||||
values: vi.fn().mockImplementation(async (data: any) => {
|
||||
@@ -76,6 +82,15 @@ const mockDb = {
|
||||
return data;
|
||||
}),
|
||||
})),
|
||||
update: vi.fn().mockImplementation((table: any) => ({
|
||||
set: vi.fn().mockImplementation((data: any) => ({
|
||||
where: vi.fn().mockImplementation(async () => {
|
||||
// Track updates
|
||||
updatedPosts.push({ id: 'updated-id', data });
|
||||
return data;
|
||||
}),
|
||||
})),
|
||||
})),
|
||||
select: vi.fn().mockReturnValue({
|
||||
from: vi.fn().mockReturnValue({
|
||||
where: vi.fn().mockResolvedValue([]),
|
||||
@@ -209,6 +224,7 @@ describe('ImportExecutionEngine E2E Tests', () => {
|
||||
// Reset all tracking arrays
|
||||
insertedPosts.length = 0;
|
||||
insertedMedia.length = 0;
|
||||
updatedPosts.length = 0;
|
||||
createdTags.length = 0;
|
||||
writtenFiles.length = 0;
|
||||
uuidCounter = 0;
|
||||
@@ -881,22 +897,20 @@ describe('ImportExecutionEngine E2E Tests', () => {
|
||||
|
||||
const result = await engine.executeImport(report, {});
|
||||
|
||||
// Post should be IMPORTED
|
||||
// Post should be IMPORTED (via update)
|
||||
expect(result.posts.imported).toBe(1);
|
||||
expect(result.posts.skipped).toBe(0);
|
||||
|
||||
// Should insert exactly one post
|
||||
expect(insertedPosts.length).toBe(1);
|
||||
// Should UPDATE existing post, not insert new one
|
||||
expect(insertedPosts.length).toBe(0);
|
||||
expect(updatedPosts.length).toBeGreaterThan(0);
|
||||
|
||||
// The inserted post MUST be a DRAFT
|
||||
expect(insertedPosts[0].status).toBe('draft');
|
||||
|
||||
// The slug should be preserved (same as conflict)
|
||||
expect(insertedPosts[0].slug).toBe('overwrite-me');
|
||||
// The updated post MUST be a DRAFT
|
||||
expect(updatedPosts[0].data.status).toBe('draft');
|
||||
|
||||
// Draft posts store content in DB, not in file
|
||||
expect(insertedPosts[0].content).not.toBeNull();
|
||||
expect(insertedPosts[0].content).toContain('conflict resolution is "overwrite"');
|
||||
expect(updatedPosts[0].data.content).not.toBeNull();
|
||||
expect(updatedPosts[0].data.content).toContain('conflict resolution is "overwrite"');
|
||||
|
||||
// No file should be written (draft = content in DB)
|
||||
const writtenFile = writtenFiles.find(f => f.path.includes('overwrite-me'));
|
||||
@@ -966,7 +980,7 @@ describe('ImportExecutionEngine E2E Tests', () => {
|
||||
expect(writtenFile).toBeDefined();
|
||||
});
|
||||
|
||||
it('should preserve WordPress dates when importing', async () => {
|
||||
it('should preserve WordPress dates when importing via update (overwrite)', async () => {
|
||||
// Post 302 has specific dates we want to preserve
|
||||
const post = wxrData.posts.find(p => p.wpId === 302);
|
||||
expect(post).toBeDefined();
|
||||
@@ -1013,19 +1027,14 @@ describe('ImportExecutionEngine E2E Tests', () => {
|
||||
|
||||
await engine.executeImport(report, {});
|
||||
|
||||
expect(insertedPosts.length).toBe(1);
|
||||
// Overwrite now updates existing post, not inserts
|
||||
expect(insertedPosts.length).toBe(0);
|
||||
expect(updatedPosts.length).toBeGreaterThan(0);
|
||||
|
||||
// Dates should come from WXR postDate and postModified
|
||||
const createdAt = insertedPosts[0].createdAt;
|
||||
const updatedAt = insertedPosts[0].updatedAt;
|
||||
|
||||
expect(createdAt).toBeInstanceOf(Date);
|
||||
expect(updatedAt).toBeInstanceOf(Date);
|
||||
|
||||
// Created from postDate
|
||||
expect(createdAt.toISOString()).toContain('2024-01-23');
|
||||
// Updated from postModified
|
||||
expect(updatedAt.toISOString()).toContain('2024-01-23T15:30');
|
||||
// For updates, the updatedAt is set to now (not the WXR date)
|
||||
// since we're updating an existing post
|
||||
const updateData = updatedPosts[0].data;
|
||||
expect(updateData.updatedAt).toBeInstanceOf(Date);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1440,15 +1449,23 @@ describe('ImportExecutionEngine E2E Tests', () => {
|
||||
// Verify result accuracy
|
||||
expect(result.success).toBe(true);
|
||||
|
||||
// Posts: 1 new imported, 1 ignore skipped, 1 overwrite imported
|
||||
expect(result.posts.imported).toBe(2); // post1 + post3
|
||||
// Posts: 1 new inserted, 1 ignore skipped, 1 overwrite updated (counts as imported)
|
||||
expect(result.posts.imported).toBe(2); // post1 (inserted) + post3 (updated)
|
||||
expect(result.posts.skipped).toBe(1); // post2 (ignore)
|
||||
expect(result.posts.errors).toBe(0);
|
||||
|
||||
// Pages: 1 imported
|
||||
// Verify that post1 was inserted and post3 was updated
|
||||
// Note: insertedPosts may include the page as well (pages are stored as posts)
|
||||
const postInserts = insertedPosts.filter(p => !JSON.parse(p.categories || '[]').includes('page'));
|
||||
const pageInserts = insertedPosts.filter(p => JSON.parse(p.categories || '[]').includes('page'));
|
||||
expect(postInserts.length).toBe(1); // post1 only (new)
|
||||
expect(updatedPosts.length).toBeGreaterThan(0); // post3 (overwrite)
|
||||
|
||||
// Pages: 1 imported (as insert since it's new)
|
||||
expect(result.pages.imported).toBe(1);
|
||||
expect(result.pages.skipped).toBe(0);
|
||||
expect(result.pages.errors).toBe(0);
|
||||
expect(pageInserts.length).toBe(1);
|
||||
|
||||
// Media: 1 imported
|
||||
expect(result.media.imported).toBe(1);
|
||||
|
||||
@@ -112,9 +112,10 @@ vi.mock('../../src/main/engine/MediaEngine', () => ({
|
||||
getMediaEngine: vi.fn(() => mockMediaEngine),
|
||||
}));
|
||||
|
||||
// Mock database - track inserts for verification
|
||||
// Mock database - track inserts and updates for verification
|
||||
const insertedPosts: any[] = [];
|
||||
const insertedMedia: any[] = [];
|
||||
const updatedPosts: { id: string; data: any }[] = [];
|
||||
|
||||
// Create a mock that tracks based on table name property
|
||||
const createMockInsert = () => ({
|
||||
@@ -128,6 +129,24 @@ const createMockInsert = () => ({
|
||||
}),
|
||||
});
|
||||
|
||||
// Create a mock that tracks post updates
|
||||
const createMockUpdate = () => {
|
||||
let updateData: any = null;
|
||||
return {
|
||||
set: vi.fn((data: any) => {
|
||||
updateData = data;
|
||||
return {
|
||||
where: vi.fn((condition: any) => {
|
||||
// Track the update with the ID being updated
|
||||
// The condition typically contains the ID
|
||||
updatedPosts.push({ id: 'updated-post-id', data: updateData });
|
||||
return Promise.resolve();
|
||||
}),
|
||||
};
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
const mockDb = {
|
||||
select: vi.fn(() => ({
|
||||
from: vi.fn(() => ({
|
||||
@@ -138,11 +157,7 @@ const mockDb = {
|
||||
})),
|
||||
})),
|
||||
insert: vi.fn(() => createMockInsert()),
|
||||
update: vi.fn(() => ({
|
||||
set: vi.fn(() => ({
|
||||
where: vi.fn().mockResolvedValue(undefined),
|
||||
})),
|
||||
})),
|
||||
update: vi.fn(() => createMockUpdate()),
|
||||
};
|
||||
|
||||
const mockClient = {
|
||||
@@ -259,6 +274,7 @@ describe('ImportExecutionEngine', () => {
|
||||
vi.clearAllMocks();
|
||||
insertedPosts.length = 0;
|
||||
insertedMedia.length = 0;
|
||||
updatedPosts.length = 0;
|
||||
engine = new ImportExecutionEngine();
|
||||
engine.setProjectContext('test-project', '/mock/project/data');
|
||||
});
|
||||
@@ -420,7 +436,7 @@ describe('ImportExecutionEngine', () => {
|
||||
expect(result.posts.skipped).toBe(1);
|
||||
});
|
||||
|
||||
it('should create draft for conflict resolution "overwrite"', async () => {
|
||||
it('should update existing post for conflict resolution "overwrite"', async () => {
|
||||
const wxrPost = createMockWxrPost();
|
||||
const analyzed = createMockAnalyzedPost(wxrPost, 'conflict', 'overwrite');
|
||||
analyzed.existingPost = {
|
||||
@@ -446,11 +462,14 @@ describe('ImportExecutionEngine', () => {
|
||||
},
|
||||
});
|
||||
|
||||
await engine.executeImport(report, {});
|
||||
const result = await engine.executeImport(report, {});
|
||||
|
||||
expect(insertedPosts.length).toBe(1);
|
||||
expect(insertedPosts[0].slug).toBe('test-post');
|
||||
expect(insertedPosts[0].status).toBe('draft');
|
||||
// Should update existing post, not insert new one
|
||||
expect(insertedPosts.length).toBe(0);
|
||||
expect(updatedPosts.length).toBeGreaterThan(0);
|
||||
expect(updatedPosts[0].data.status).toBe('draft');
|
||||
expect(updatedPosts[0].data.title).toBe(wxrPost.title);
|
||||
expect(result.posts.imported).toBe(1);
|
||||
});
|
||||
|
||||
it('should create new post with new slug for conflict resolution "import"', async () => {
|
||||
@@ -644,7 +663,7 @@ describe('ImportExecutionEngine', () => {
|
||||
expect(insertedPosts[0].publishedAt).toEqual(pubDate);
|
||||
});
|
||||
|
||||
it('should not set publishedAt for draft posts', async () => {
|
||||
it('should not set publishedAt for overwrite draft posts', async () => {
|
||||
const wxrPost = createMockWxrPost();
|
||||
const analyzed = createMockAnalyzedPost(wxrPost, 'conflict', 'overwrite');
|
||||
analyzed.existingPost = {
|
||||
@@ -672,9 +691,11 @@ describe('ImportExecutionEngine', () => {
|
||||
|
||||
await engine.executeImport(report, {});
|
||||
|
||||
expect(insertedPosts.length).toBe(1);
|
||||
expect(insertedPosts[0].status).toBe('draft');
|
||||
expect(insertedPosts[0].publishedAt).toBeUndefined();
|
||||
// Should update, not insert
|
||||
expect(insertedPosts.length).toBe(0);
|
||||
expect(updatedPosts.length).toBeGreaterThan(0);
|
||||
expect(updatedPosts[0].data.status).toBe('draft');
|
||||
expect(updatedPosts[0].data.publishedAt).toBeNull();
|
||||
});
|
||||
|
||||
it('should handle post without optional fields', async () => {
|
||||
@@ -817,10 +838,11 @@ describe('ImportExecutionEngine', () => {
|
||||
|
||||
await engine.executeImport(report, {});
|
||||
|
||||
expect(insertedPosts.length).toBe(1);
|
||||
// Overwrite updates existing post, not inserts
|
||||
expect(updatedPosts.length).toBeGreaterThan(0);
|
||||
// Draft posts store content in DB
|
||||
expect(insertedPosts[0].content).toContain('[[gallery ids="1,2"]]');
|
||||
expect(insertedPosts[0].content).toContain('[[video src="test.mp4"]]');
|
||||
expect(updatedPosts[0].data.content).toContain('[[gallery ids="1,2"]]');
|
||||
expect(updatedPosts[0].data.content).toContain('[[video src="test.mp4"]]');
|
||||
});
|
||||
|
||||
it('should not escape underscores inside macro names during markdown conversion', async () => {
|
||||
@@ -852,9 +874,10 @@ describe('ImportExecutionEngine', () => {
|
||||
|
||||
await engine.executeImport(report, {});
|
||||
|
||||
expect(insertedPosts.length).toBe(1);
|
||||
expect(insertedPosts[0].content).toContain('[[photo_archive]]');
|
||||
expect(insertedPosts[0].content).not.toContain('photo\\_archive');
|
||||
// Overwrite updates existing post, not inserts
|
||||
expect(updatedPosts.length).toBeGreaterThan(0);
|
||||
expect(updatedPosts[0].data.content).toContain('[[photo_archive]]');
|
||||
expect(updatedPosts[0].data.content).not.toContain('photo\\_archive');
|
||||
});
|
||||
|
||||
it('should not escape underscores in macro attributes during markdown conversion', async () => {
|
||||
@@ -886,8 +909,9 @@ describe('ImportExecutionEngine', () => {
|
||||
|
||||
await engine.executeImport(report, {});
|
||||
|
||||
expect(insertedPosts.length).toBe(1);
|
||||
expect(insertedPosts[0].content).toContain('[[my_gallery type="grid_view" size="large_thumb"]]');
|
||||
// Overwrite updates existing post, not inserts
|
||||
expect(updatedPosts.length).toBeGreaterThan(0);
|
||||
expect(updatedPosts[0].data.content).toContain('[[my_gallery type="grid_view" size="large_thumb"]]');
|
||||
});
|
||||
|
||||
it('should map tags based on analysis mappings', async () => {
|
||||
@@ -1489,9 +1513,11 @@ describe('ImportExecutionEngine', () => {
|
||||
|
||||
await engine.executeImport(report, {});
|
||||
|
||||
expect(insertedPosts.length).toBe(1);
|
||||
expect(insertedPosts[0].status).toBe('draft');
|
||||
const categories = JSON.parse(insertedPosts[0].categories);
|
||||
// Should update existing page, not insert new one
|
||||
expect(insertedPosts.length).toBe(0);
|
||||
expect(updatedPosts.length).toBeGreaterThan(0);
|
||||
expect(updatedPosts[0].data.status).toBe('draft');
|
||||
const categories = JSON.parse(updatedPosts[0].data.categories);
|
||||
expect(categories).toContain('page');
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user