require 'rails_helper' RSpec.describe MaterialSyncRunner do let(:user) { create(:user, :member) } let(:source) do MaterialSyncSource.create!( name: 'Drive source', source_kind: 'google_drive_path', source_file_id: 'folder-123', profile: 'legacy_drive', created_by_user: user, updated_by_user: user) end let(:drive_client) { instance_double(GoogleDrive::ApiClient) } describe '#sync!' do it 'groups created and updated materials across sync sources into one event' do existing = Material.create!( source_kind: 'google_drive_file', source_file_id: 'existing-file', source_path: 'old.png', url: 'https://example.com/existing', created_by_user: user, updated_by_user: user) source MaterialSyncSource.create!( name: 'Second Drive source', source_kind: 'google_drive_path', source_file_id: 'folder-456', profile: 'legacy_drive', created_by_user: user, updated_by_user: user) allow(GoogleDrive::ApiClient).to receive(:new).and_return(drive_client) allow(drive_client).to receive(:each_material_file_under_folder) do |folder_id, &block| id = folder_id == 'folder-123' ? 'existing-file' : 'new-file' block.call(id:, name: "#{ id }.png", mime_type: 'image/png', relative_path: "素材/#{ id }.png", sha256_checksum: nil, web_view_link: "https://drive.google.com/file/d/#{ id }/view") end allow(drive_client).to receive(:download_to_tempfile) do |id, **| Tempfile.new(['sync-event', '.png']).tap do |file| file.binmode file.write(id) file.rewind end end allow(MaterialThumbnailGenerator).to receive(:generate!) previous_ids = MaterialVersion.pluck(:id) result = nil expect { result = described_class.sync_enabled! } .to change { event_rows.length }.by(1) expect(result).to have_attributes(imported: 1, updated: 1, failed: 0) event = event_rows.last expect(event.fetch('event_type')).to eq('materials.sync') created = Material.find_by!(source_file_id: 'new-file') versions = MaterialVersion.where.not(id: previous_ids) expect(versions.pluck(:material_id).uniq).to contain_exactly(existing.id, created.id) expect(versions.pluck(:event_type)).to include('create', 'update') expect(versions.pluck(:event_id).uniq).to eq([event.fetch('id')]) end it 'imports google drive path candidates as they are yielded' do first_entry = { id: 'file-1', name: 'a.png', mime_type: 'image/png', relative_path: '素材/a.png', sha256_checksum: 'sha-a', web_view_link: 'https://drive.google.com/file/d/file-1/view', web_content_link: nil } second_entry = { id: 'file-2', name: 'b.png', mime_type: 'image/png', relative_path: '素材/b.png', sha256_checksum: 'sha-b', web_view_link: 'https://drive.google.com/file/d/file-2/view', web_content_link: nil } yielded = [] allow(GoogleDrive::ApiClient).to receive(:new).and_return(drive_client) allow(drive_client).to receive(:each_material_file_under_folder) do |folder_id, &block| expect(folder_id).to eq('folder-123') block.call(first_entry) expect(yielded).to eq(['素材/a.png']) block.call(second_entry) end allow(MaterialSyncImporter).to receive(:import!) do |candidate| yielded << candidate.fetch(:source_path) instance_double(MaterialSyncImporter::Result, action: yielded.last == '素材/a.png' ? :imported : :updated) end result = described_class.new(source).sync! expect(yielded).to eq(['素材/a.png', '素材/b.png']) expect(result.imported).to eq(1) expect(result.updated).to eq(1) expect(result.unchanged).to eq(0) expect(source.reload.last_synced_at).to be_present end it 'logs google drive path progress at first item and summary' do entry = { id: 'file-1', name: 'a.png', mime_type: 'image/png', relative_path: '素材/a.png', sha256_checksum: 'sha-a', web_view_link: 'https://drive.google.com/file/d/file-1/view', web_content_link: nil } logged = [] allow(GoogleDrive::ApiClient).to receive(:new).and_return(drive_client) allow(drive_client).to receive(:each_material_file_under_folder) do |_folder_id, &block| block.call(entry) end allow(MaterialSyncImporter).to receive(:import!) .and_return(instance_double(MaterialSyncImporter::Result, action: :imported)) allow(Rails.logger).to receive(:info) { |message| logged << JSON.parse(message) } described_class.new(source).sync! progress_logs = logged.select { |row| row['folder_id'] == 'folder-123' } expect(progress_logs.map { |row| row['progress'] }).to eq(['scan', 'summary']) expect(progress_logs.last).to include( 'material_sync_source_id' => source.id, 'material_sync_source_name' => 'Drive source', 'scanned_count' => 1, 'imported' => 1, 'updated' => 0, 'unchanged' => 0, 'suppressed' => 0, 'failed' => 0) end end end