wizard
This commit is contained in:
4
.env
4
.env
@@ -5,5 +5,5 @@ DATABASE_URL=postgresql://postgres:password@localhost:5432/email_organizer_dev
|
|||||||
# OPENAI_MODEL=qwen/qwen3-coder
|
# OPENAI_MODEL=qwen/qwen3-coder
|
||||||
|
|
||||||
OPENAI_API_KEY=aaoeu
|
OPENAI_API_KEY=aaoeu
|
||||||
OPENAI_BASE_URL=http://localhost:5082/v1
|
OPENAI_BASE_URL=http://workstation:5082/v1
|
||||||
OPENAI_MODEL=Qwen3-Coder-480B-A35B-Instruct-GGUF-roo
|
OPENAI_MODEL=Qwen3-235B-A22B-Thinking-2507-GGUF
|
||||||
|
|||||||
@@ -138,11 +138,9 @@ def test_imap_connection():
|
|||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
# Redirect to folder selection modal after successful connection
|
# Redirect to folder selection modal after successful connection
|
||||||
response = make_response('')
|
return imap_folders_modal()
|
||||||
response.headers['HX-Trigger'] = 'open-modal'
|
#response.headers['HX-Trigger'] = 'open-modal'
|
||||||
response.headers['HX-location'] = '/api/imap/folders/modal'
|
|
||||||
else:
|
else:
|
||||||
print(message)
|
|
||||||
response = make_response(render_template('partials/imap_config_modal.html',
|
response = make_response(render_template('partials/imap_config_modal.html',
|
||||||
errors={'general': message}, server=server, port=port, username=username, use_ssl=use_ssl))
|
errors={'general': message}, server=server, port=port, username=username, use_ssl=use_ssl))
|
||||||
response.headers['HX-Retarget'] = '#imap-modal'
|
response.headers['HX-Retarget'] = '#imap-modal'
|
||||||
@@ -315,114 +313,4 @@ def sync_selected_folders():
|
|||||||
logging.exception("Error syncing selected IMAP folders: %s", e)
|
logging.exception("Error syncing selected IMAP folders: %s", e)
|
||||||
print(e)
|
print(e)
|
||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
return jsonify({'error': 'An unexpected error occurred'}), 500
|
|
||||||
|
|
||||||
@imap_bp.route('/api/imap/sync', methods=['POST'])
|
|
||||||
@login_required
|
|
||||||
def sync_imap_folders():
|
|
||||||
"""Create and sync folders from IMAP server with processed email tracking."""
|
|
||||||
try:
|
|
||||||
if not current_user.imap_config:
|
|
||||||
return jsonify({'error': 'No IMAP configuration found. Please configure IMAP first.'}), 400
|
|
||||||
|
|
||||||
# Test connection first
|
|
||||||
imap_service = IMAPService(current_user)
|
|
||||||
|
|
||||||
# Get folders from IMAP server
|
|
||||||
imap_folders = imap_service.get_folders()
|
|
||||||
|
|
||||||
if not imap_folders:
|
|
||||||
return jsonify({'error': 'No folders found on IMAP server'}), 400
|
|
||||||
|
|
||||||
# Deduplicate folders by name to prevent creating multiple entries for the same folder
|
|
||||||
unique_folders = []
|
|
||||||
seen_names = set()
|
|
||||||
for imap_folder in imap_folders:
|
|
||||||
folder_name = imap_folder['name']
|
|
||||||
|
|
||||||
# Skip special folders that might not be needed
|
|
||||||
if folder_name.lower() in ['sent', 'drafts', 'spam', 'trash']:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Use case-insensitive comparison for deduplication
|
|
||||||
folder_name_lower = folder_name.lower()
|
|
||||||
if folder_name_lower not in seen_names:
|
|
||||||
unique_folders.append(imap_folder)
|
|
||||||
seen_names.add(folder_name_lower)
|
|
||||||
|
|
||||||
# Process each unique folder
|
|
||||||
synced_count = 0
|
|
||||||
processed_emails_service = ProcessedEmailsService(current_user)
|
|
||||||
|
|
||||||
# Create a list of folders to process
|
|
||||||
folders_to_process = []
|
|
||||||
|
|
||||||
for imap_folder in unique_folders:
|
|
||||||
folder_name = imap_folder['name'].strip()
|
|
||||||
|
|
||||||
# Handle nested folder names (convert slashes to underscores or keep as-is)
|
|
||||||
# According to requirements, nested folders should be created with slashes in the name
|
|
||||||
display_name = folder_name
|
|
||||||
|
|
||||||
# Check if folder already exists
|
|
||||||
existing_folder = Folder.query.filter_by(
|
|
||||||
user_id=current_user.id,
|
|
||||||
name=display_name
|
|
||||||
).first()
|
|
||||||
|
|
||||||
if not existing_folder:
|
|
||||||
# Create new folder
|
|
||||||
# Determine folder type - inbox should be 'tidy', others 'destination'
|
|
||||||
folder_type = 'tidy' if folder_name.lower().strip() == 'inbox' else 'destination'
|
|
||||||
|
|
||||||
new_folder = Folder(
|
|
||||||
user_id=current_user.id,
|
|
||||||
name=display_name,
|
|
||||||
rule_text=f"Auto-synced from IMAP folder: {folder_name}",
|
|
||||||
priority=0, # Default priority
|
|
||||||
folder_type=folder_type
|
|
||||||
)
|
|
||||||
db.session.add(new_folder)
|
|
||||||
synced_count += 1
|
|
||||||
folders_to_process.append(new_folder)
|
|
||||||
else:
|
|
||||||
# Update existing folder with email counts and recent emails
|
|
||||||
# Get all email UIDs in this folder
|
|
||||||
email_uids = imap_service.get_folder_email_uids(folder_name)
|
|
||||||
|
|
||||||
# Sync with processed emails service
|
|
||||||
new_emails_count = processed_emails_service.sync_folder_emails(display_name, email_uids)
|
|
||||||
print("NEW", new_emails_count)
|
|
||||||
|
|
||||||
# Update counts
|
|
||||||
pending_count = processed_emails_service.get_pending_count(display_name)
|
|
||||||
existing_folder.pending_count = pending_count
|
|
||||||
existing_folder.total_count = len(email_uids)
|
|
||||||
|
|
||||||
# Get the most recent emails for this folder
|
|
||||||
recent_emails = imap_service.get_recent_emails(folder_name, 3)
|
|
||||||
existing_folder.recent_emails = recent_emails
|
|
||||||
|
|
||||||
folders_to_process.append(existing_folder)
|
|
||||||
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
# Check if we should show the folder type selection modal
|
|
||||||
# Only show the modal if there are new folders to configure
|
|
||||||
if synced_count > 0:
|
|
||||||
# Return the folder type selection modal
|
|
||||||
response = make_response(render_template('partials/folder_type_selection_modal.html', folders=folders_to_process))
|
|
||||||
response.headers['hx-retarget'] = "#modal-holder"
|
|
||||||
response.headers['HX-Trigger'] = 'open-modal'
|
|
||||||
return response
|
|
||||||
else:
|
|
||||||
# Just trigger the folder list update
|
|
||||||
response = make_response('')
|
|
||||||
response.headers['HX-Trigger'] = 'close-modal, folder-list-invalidated'
|
|
||||||
return response
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logging.exception("Error syncing IMAP folders: %s", e)
|
|
||||||
print(e)
|
|
||||||
db.session.rollback()
|
|
||||||
return jsonify({'error': 'An unexpected error occurred'}), 500
|
return jsonify({'error': 'An unexpected error occurred'}), 500
|
||||||
@@ -38,7 +38,12 @@
|
|||||||
/* Fade out transition for HTMX */
|
/* Fade out transition for HTMX */
|
||||||
.fade-out-htmx.htmx-swapping {
|
.fade-out-htmx.htmx-swapping {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: opacity 1s ease-out;
|
transition: opacity 280ms ease-out;
|
||||||
|
}
|
||||||
|
/* Fade out transition for HTMX */
|
||||||
|
.htmx-swapping .fade-out-htmx {
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 280ms ease-out;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{% block head %}{% endblock %}
|
{% block head %}{% endblock %}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<div id="folder-selection-modal" class="modal-box step-2 slide-left-enter-from w-11/12 max-w-4xl" @click.away="$refs.modal.close()">
|
<div id="folder-selection-modal" class="modal-box step-2 w-11/12 max-w-4xl fade-in-htmx" @click.away="$refs.modal.close()">
|
||||||
<div class="flex items-center mb-4">
|
<div class="flex items-center mb-4">
|
||||||
<div class="steps flex-1">
|
<div class="steps flex-1">
|
||||||
<span class="step">Step 1</span>
|
<span class="step">Step 1</span>
|
||||||
@@ -6,9 +6,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<h3 class="font-bold text-lg">Configure IMAP Folders</h3>
|
<h3 class="font-bold text-lg">Configure IMAP Folders</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="mb-4">The following folders were found on your IMAP server. Select which folders you want to sync and configure their processing types.</p>
|
<p class="mb-4">The following folders were found on your IMAP server. Select which folders you want to sync and configure their processing types.</p>
|
||||||
<form id="folder-selection-form" hx-post="/api/imap/sync-selected" hx-target="#modal-holder" hx-swap="innerHTML slide-left:300ms">
|
<form id="folder-selection-form" hx-post="/api/imap/sync-selected" hx-target="#modal-holder" hx-swap="innerHTML">
|
||||||
|
|
||||||
<div class="overflow-x-auto mb-4">
|
<div class="overflow-x-auto mb-4">
|
||||||
<table class="table">
|
<table class="table">
|
||||||
|
|||||||
@@ -1,59 +0,0 @@
|
|||||||
<div id="folder-type-modal" class="modal-box step-2 slide-left-enter-from" @click.away="$refs.modal.close()">
|
|
||||||
<div class="flex items-center mb-4">
|
|
||||||
<div class="steps flex-1">
|
|
||||||
<span class="step">Step 1</span>
|
|
||||||
<span class="step step-primary">Step 2</span>
|
|
||||||
</div>
|
|
||||||
<h3 class="font-bold text-lg">Configure Folder Types</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p class="mb-4">Select the processing type for each folder. Inbox will default to Tidy, while Archive/Spam/Drafts will default to Ignore.</p>
|
|
||||||
|
|
||||||
<div class="overflow-x-auto mb-4">
|
|
||||||
<table class="table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Folder Name</th>
|
|
||||||
<th>Processing Type</th>
|
|
||||||
<th>Description</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for folder in folders %}
|
|
||||||
<tr>
|
|
||||||
<td>{{ folder.name }}</td>
|
|
||||||
<td>
|
|
||||||
<select class="select select-bordered select-sm"
|
|
||||||
name="folder_type_{{ folder.id }}"
|
|
||||||
hx-put="/api/folders/{{ folder.id }}/type"
|
|
||||||
hx-target="#modal-holder"
|
|
||||||
hx-swap="outerHTML">
|
|
||||||
<option value="tidy" {% if folder.folder_type == 'tidy' %}selected{% endif %}>Tidy</option>
|
|
||||||
<option value="destination" {% if folder.folder_type == 'destination' %}selected{% endif %}>Destination</option>
|
|
||||||
<option value="ignore" {% if folder.folder_type == 'ignore' %}selected{% endif %}>Ignore</option>
|
|
||||||
</select>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{% if folder.folder_type == 'tidy' %}
|
|
||||||
Processed by AI to organize emails
|
|
||||||
{% elif folder.folder_type == 'destination' %}
|
|
||||||
Target for organized emails
|
|
||||||
{% else %}
|
|
||||||
Ignored during processing
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="modal-action">
|
|
||||||
<button type="button" class="btn btn-outline" hx-get="/api/imap/config" hx-target="#modal-holder" hx-swap="innerHTML slide-right:300ms">
|
|
||||||
<i class="fas fa-arrow-left mr-2"></i>Back
|
|
||||||
</button>
|
|
||||||
<button type="button" class="btn btn-primary" hx-post="/api/imap/sync">
|
|
||||||
Save and Continue
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<div id="imap-modal" @click.away="$refs.modal.close()" class="modal-box step-1 translate-up-enter-from w-11/12 max-w-4xl" x-data="{ errors: {{ 'true' if errors else 'false' }} }" x-init="$nextTick(() => { if (errors) { document.querySelector('#submit-btn').classList.add('shake'); } })" >
|
<div id="imap-modal" @click.away="$refs.modal.close()" class="modal-box step-1 w-11/12 max-w-4xl fade-out-htmx" x-data="{ errors: {{ 'true' if errors else 'false' }} }" x-init="$nextTick(() => { if (errors) { document.querySelector('#submit-btn').classList.add('shake'); } })" >
|
||||||
<div class="flex items-center mb-4">
|
<div class="flex items-center mb-4">
|
||||||
<div class="steps flex-1">
|
<div class="steps flex-1">
|
||||||
<span class="step step-primary">Step 1</span>
|
<span class="step step-primary">Step 1</span>
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<form id="imap-form" hx-post="/api/imap/test" hx-target="#imap-modal" hx-swap="outerHTML">
|
<form id="imap-form" hx-post="/api/imap/test" hx-target="#modal-holder" hx-swap="innerHTML swap:300ms" hx-trigger="submit once">
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label for="imap-server" class="block text-sm font-medium mb-1">IMAP Server</label>
|
<label for="imap-server" class="block text-sm font-medium mb-1">IMAP Server</label>
|
||||||
<input type="text" id="imap-server" name="server"
|
<input type="text" id="imap-server" name="server"
|
||||||
@@ -84,12 +84,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{% if success %}
|
|
||||||
<div class="mt-4 pt-4 border-t border-base-300" data-loading-states>
|
|
||||||
<button class="btn btn-success w-full" hx-post="/api/imap/sync" hx-target="#modal-holder" hx-swap="innerHTML slide-left:300ms" data-loading-disable>
|
|
||||||
<span data-loading-class="!hidden"><i class="fas fa-sync mr-2"></i>Configure Folder Types</span>
|
|
||||||
<span class="loading loading-spinner loading-xs hidden" data-loading-class-remove="hidden"></span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
@@ -77,18 +77,17 @@ def test_folder_deletion_and_resync_flow(playwright: playwright.sync_api.Playwri
|
|||||||
page.click("button[type='submit']")
|
page.click("button[type='submit']")
|
||||||
|
|
||||||
# Wait for connection test to complete
|
# Wait for connection test to complete
|
||||||
page.wait_for_selector("button:has-text('Configure Folder Types')")
|
page.wait_for_selector("h3:has-text('Configure IMAP Folders')")
|
||||||
|
|
||||||
# Step 5: Sync folders and verify they exist
|
|
||||||
# Click the "Configure Folder Types" button
|
|
||||||
page.click("button:has-text('Configure Folder Types')")
|
|
||||||
|
|
||||||
# Step 5: Click "Save and Continue" on the final modal
|
# Step 5: Click "Save and Continue" on the final modal
|
||||||
page.click("button:has-text('Save and Continue')")
|
page.click("button:has-text('Save and Continue')")
|
||||||
|
|
||||||
# Wait for modal to close and navigation to complete
|
# Wait for modal to close and navigation to complete
|
||||||
page.wait_for_load_state("networkidle")
|
page.wait_for_load_state("networkidle")
|
||||||
|
|
||||||
|
# Wait for modal to close and navigation to complete
|
||||||
|
page.wait_for_load_state("networkidle")
|
||||||
|
|
||||||
# Wait for modal to appear and folders to sync
|
# Wait for modal to appear and folders to sync
|
||||||
page.wait_for_selector("#folders-list", timeout=10000)
|
page.wait_for_selector("#folders-list", timeout=10000)
|
||||||
|
|
||||||
|
|||||||
@@ -77,11 +77,7 @@ def test_full_user_flow(playwright: playwright.sync_api.Playwright):
|
|||||||
page.click("button[type='submit']")
|
page.click("button[type='submit']")
|
||||||
|
|
||||||
# Wait for connection test to complete
|
# Wait for connection test to complete
|
||||||
page.wait_for_selector("button:has-text('Configure Folder Types')")
|
page.wait_for_selector("h3:has-text('Configure IMAP Folders')")
|
||||||
|
|
||||||
# Step 4: Sync folders
|
|
||||||
# Click the "Configure Folder Types" button
|
|
||||||
page.click("button:has-text('Configure Folder Types')")
|
|
||||||
|
|
||||||
# Step 5: Click "Save and Continue" on the final modal
|
# Step 5: Click "Save and Continue" on the final modal
|
||||||
page.click("button:has-text('Save and Continue')")
|
page.click("button:has-text('Save and Continue')")
|
||||||
|
|||||||
@@ -19,17 +19,155 @@ class TestIMAPRoutes:
|
|||||||
print(response.data)
|
print(response.data)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
# Should show either success or error message
|
# After successful connection, it should show the folder selection modal
|
||||||
assert b'Test Connection' in response.data
|
assert b'Configure IMAP Folders' in response.data
|
||||||
|
|
||||||
def test_imap_sync_folders(self, authenticated_client, app, mock_user):
|
def test_imap_sync_selected_folders_no_config(self, authenticated_client):
|
||||||
# Create a test user and log in
|
"""Test sync-selected fails when no IMAP config is set"""
|
||||||
|
response = authenticated_client.post('/api/imap/sync-selected', data={})
|
||||||
|
assert response.status_code == 400
|
||||||
|
assert b'No IMAP configuration found' in response.data
|
||||||
|
|
||||||
|
def test_imap_sync_selected_folders_no_selection(self, authenticated_client, mock_user):
|
||||||
|
"""Test sync-selected fails when no folders are selected"""
|
||||||
|
# Set up IMAP config
|
||||||
mock_user.imap_config = {'server': 'localhost', 'port': 5143, 'username': 'user1@example.com', 'password': 'password1', 'use_ssl': False}
|
mock_user.imap_config = {'server': 'localhost', 'port': 5143, 'username': 'user1@example.com', 'password': 'password1', 'use_ssl': False}
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
folders = Folder.query.filter_by(user_id=mock_user.id).all()
|
|
||||||
response = authenticated_client.post('/api/imap/sync')
|
response = authenticated_client.post('/api/imap/sync-selected', data={})
|
||||||
print('respo', response.data, response)
|
assert response.status_code == 400
|
||||||
|
assert b'No folders selected' in response.data
|
||||||
|
|
||||||
|
def test_imap_sync_selected_folders_with_real_folders(self, authenticated_client, mock_user):
|
||||||
|
"""Test sync-selected succeeds with real folders from the IMAP server"""
|
||||||
|
# Set up IMAP config
|
||||||
|
mock_user.imap_config = {'server': 'localhost', 'port': 5143, 'username': 'user1@example.com', 'password': 'password1', 'use_ssl': False}
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
# Get initial folder count
|
||||||
|
initial_folders = Folder.query.filter_by(user_id=mock_user.id).all()
|
||||||
|
initial_count = len(initial_folders)
|
||||||
|
|
||||||
|
# Use real folder names from the IMAP server that we saw in the test output
|
||||||
|
# Note: The endpoint strips leading/trailing spaces before storing
|
||||||
|
form_data = {
|
||||||
|
'folder_0': ' Receipts',
|
||||||
|
'folder_type_0': 'destination',
|
||||||
|
'folder_1': ' Marketing',
|
||||||
|
'folder_type_1': 'tidy'
|
||||||
|
}
|
||||||
|
|
||||||
|
response = authenticated_client.post('/api/imap/sync-selected', data=form_data)
|
||||||
|
|
||||||
|
# Should return 200 with proper headers
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.headers.get('HX-Trigger') == 'close-modal, folder-list-invalidated'
|
||||||
|
|
||||||
|
# Check that new folders were created
|
||||||
new_folders = Folder.query.filter_by(user_id=mock_user.id).all()
|
new_folders = Folder.query.filter_by(user_id=mock_user.id).all()
|
||||||
assert len(new_folders) > len(folders)
|
assert len(new_folders) > initial_count
|
||||||
# Should fail without real IMAP server but return proper response
|
|
||||||
assert response.status_code in [200, 400]
|
# Verify folder types were set correctly (names are stripped before storing)
|
||||||
|
receipts_folder = Folder.query.filter_by(user_id=mock_user.id, name='Receipts').first()
|
||||||
|
marketing_folder = Folder.query.filter_by(user_id=mock_user.id, name='Marketing').first()
|
||||||
|
|
||||||
|
assert receipts_folder is not None
|
||||||
|
assert receipts_folder.folder_type == 'destination'
|
||||||
|
assert marketing_folder is not None
|
||||||
|
assert marketing_folder.folder_type == 'tidy'
|
||||||
|
|
||||||
|
def test_imap_sync_selected_folders_existing_folders(self, authenticated_client, mock_user):
|
||||||
|
"""Test sync-selected updates existing folders instead of creating duplicates"""
|
||||||
|
# Set up IMAP config
|
||||||
|
mock_user.imap_config = {'server': 'localhost', 'port': 5143, 'username': 'user1@example.com', 'password': 'password1', 'use_ssl': False}
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
# Clean up any existing folders first
|
||||||
|
Folder.query.filter_by(user_id=mock_user.id).delete()
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
# Create an existing folder (without leading spaces since they get stripped)
|
||||||
|
existing_folder = Folder(
|
||||||
|
user_id=mock_user.id,
|
||||||
|
name='Receipts', # This is how it will be stored after stripping
|
||||||
|
folder_type='destination',
|
||||||
|
total_count=5,
|
||||||
|
pending_count=2
|
||||||
|
)
|
||||||
|
db.session.add(existing_folder)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
initial_count = Folder.query.filter_by(user_id=mock_user.id).count()
|
||||||
|
|
||||||
|
# Select the existing folder and a new one
|
||||||
|
form_data = {
|
||||||
|
'folder_0': ' Receipts', # IMAP returns with space, but gets stripped
|
||||||
|
'folder_type_0': 'tidy', # Different type to test update
|
||||||
|
'folder_1': ' Personal',
|
||||||
|
'folder_type_1': 'destination'
|
||||||
|
}
|
||||||
|
|
||||||
|
response = authenticated_client.post('/api/imap/sync-selected', data=form_data)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
# Check that we have exactly one more folder (the new one)
|
||||||
|
final_folders = Folder.query.filter_by(user_id=mock_user.id).all()
|
||||||
|
assert len(final_folders) == initial_count + 1
|
||||||
|
|
||||||
|
# Check that existing folder type was updated
|
||||||
|
updated_folder = Folder.query.filter_by(user_id=mock_user.id, name='Receipts').first()
|
||||||
|
assert updated_folder is not None
|
||||||
|
assert updated_folder.folder_type == 'tidy'
|
||||||
|
|
||||||
|
# Check that new folder was created (name gets stripped)
|
||||||
|
new_folder = Folder.query.filter_by(user_id=mock_user.id, name='Personal').first()
|
||||||
|
assert new_folder is not None
|
||||||
|
assert new_folder.folder_type == 'destination'
|
||||||
|
|
||||||
|
def test_imap_sync_selected_folders_special_folders_skipped(self, authenticated_client, mock_user):
|
||||||
|
"""Test sync-selected skips special folders like sent, drafts, spam, trash"""
|
||||||
|
# Set up IMAP config
|
||||||
|
mock_user.imap_config = {'server': 'localhost', 'port': 5143, 'username': 'user1@example.com', 'password': 'password1', 'use_ssl': False}
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
initial_count = Folder.query.filter_by(user_id=mock_user.id).count()
|
||||||
|
|
||||||
|
# Clean up any existing folders first
|
||||||
|
Folder.query.filter_by(user_id=mock_user.id).delete()
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
initial_count = Folder.query.filter_by(user_id=mock_user.id).count()
|
||||||
|
|
||||||
|
# Try to select special folders along with real folders
|
||||||
|
form_data = {
|
||||||
|
'folder_0': 'Sent',
|
||||||
|
'folder_type_0': 'destination',
|
||||||
|
'folder_1': 'Drafts',
|
||||||
|
'folder_type_1': 'tidy',
|
||||||
|
'folder_2': 'Spam',
|
||||||
|
'folder_type_2': 'destination',
|
||||||
|
'folder_3': 'Trash',
|
||||||
|
'folder_type_3': 'tidy',
|
||||||
|
'folder_4': ' Work',
|
||||||
|
'folder_type_4': 'destination'
|
||||||
|
}
|
||||||
|
|
||||||
|
response = authenticated_client.post('/api/imap/sync-selected', data=form_data)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
# Check that only Work folder was created (special folders should be skipped)
|
||||||
|
final_folders = Folder.query.filter_by(user_id=mock_user.id).all()
|
||||||
|
assert len(final_folders) == initial_count + 1
|
||||||
|
|
||||||
|
# Check that Work folder exists (name gets stripped)
|
||||||
|
work_folder = Folder.query.filter_by(user_id=mock_user.id, name='Work').first()
|
||||||
|
assert work_folder is not None
|
||||||
|
assert work_folder.folder_type == 'destination'
|
||||||
|
|
||||||
|
# Check that special folders don't exist
|
||||||
|
special_folders = ['Sent', 'Drafts', 'Spam', 'Trash']
|
||||||
|
for folder_name in special_folders:
|
||||||
|
folder = Folder.query.filter_by(user_id=mock_user.id, name=folder_name).first()
|
||||||
|
assert folder is None, f"Special folder {folder_name} should not have been created"
|
||||||
|
|||||||
Reference in New Issue
Block a user