require 'rails_helper' RSpec.describe MaterialSyncImporter do let(:user) { create(:user, :member) } let(:tag) { Tag.create!(tag_name: TagName.create!(name: 'sync_tag'), category: :material) } def tempfile_for body Tempfile.new(['material-sync-importer', '.png']).tap do |file| file.binmode file.write(body) file.rewind end end it 'creates an untagged material from a sync candidate with file_downloader' do result = described_class.import!( source_kind: 'google_drive_file', source_path: '素材/a.png', source_file_id: 'drive-file-a', normalized_source_key: 'google_drive_file:drive-file-a', filename: 'a.png', content_type: 'image/png', file_sha256: Digest::SHA256.hexdigest('sync-body'), file_downloader: -> { tempfile_for('sync-body') }, export_path: '素材/a.png', tag: nil, url: nil, created_by_user: user, updated_by_user: user) material = result.material.reload expect(result.action).to eq(:imported) expect(material.tag).to be_nil expect(material.url).to be_nil expect(material.file).to be_attached expect(material.file.blob.filename.to_s).to eq('a.png') expect(material.material_export_items.first.export_path).to eq('素材/a.png') end it 'does not clear a human-assigned tag or URL on existing Drive sync' do material = Material.create!(tag:, url: 'https://example.com/human', source_kind: 'google_drive_file', source_path: '素材/a.png', source_file_id: 'drive-file-a', created_by_user: user, updated_by_user: user) material.file.attach( io: StringIO.new('same-body'), filename: 'a.png', content_type: 'image/png') material.file.blob.metadata['sha256'] = Digest::SHA256.hexdigest('same-body') material.file.blob.save! MaterialExportItem.create!(material:, profile: 'legacy_drive', export_path: '素材/a.png', created_by_user: user) result = described_class.import!( source_kind: 'google_drive_file', source_path: '素材/a.png', source_file_id: 'drive-file-a', filename: 'a.png', content_type: 'image/png', file_sha256: Digest::SHA256.hexdigest('same-body'), export_path: '素材/a.png', tag: nil, url: nil, updated_by_user: user) expect(result.action).to eq(:unchanged) expect(material.reload.tag).to eq(tag) expect(material.url).to eq('https://example.com/human') end it 'suppresses a Google Drive candidate by relative path prefix' do MaterialSyncSuppression.create!(source_kind: 'google_drive_path_prefix', drive_path: '素材/危険', reason: 'copyright_high_risk', created_by_user: user) result = described_class.import!( source_kind: 'google_drive_file', source_path: '素材/危険/a.png', source_file_id: 'drive-file-b', filename: 'a.png', content_type: 'image/png', file_sha256: Digest::SHA256.hexdigest('blocked-body'), file_downloader: -> { tempfile_for('blocked-body') }, export_path: '素材/危険/a.png', updated_by_user: user) expect(result.action).to eq(:suppressed) expect(result.suppression.normalized_source_key) .to eq('google_drive_path_prefix:素材/危険') expect(Material.find_by(source_file_id: 'drive-file-b')).to be_nil end it 'rechecks import blocks after downloaded file sha256 is known' do MaterialImportBlock.create!(match_kind: 'sha256', sha256: Digest::SHA256.hexdigest('blocked-body'), reason: 'copyright_high_risk', created_by_user: user) blob_count = ActiveStorage::Blob.count expect do result = described_class.import!( source_kind: 'google_drive_file', source_path: '素材/blocked.png', source_file_id: 'drive-file-blocked', filename: 'blocked.png', content_type: 'image/png', file_downloader: -> { tempfile_for('blocked-body') }, export_path: '素材/blocked.png', updated_by_user: user) expect(result.action).to eq(:suppressed) expect(result.suppression.reason).to eq('copyright_high_risk') end.not_to change(Material, :count) expect(ActiveStorage::Blob.count).to eq(blob_count) expect(Material.find_by(source_file_id: 'drive-file-blocked')).to be_nil end end