Repository: adobe/spacecat-audit-worker
Pull request #829: feat: Add stepped audit support for meta-tags
Author: login: dipratap
Labels: name: released
name: WIP
description: work in progress
Created: 2025-05-05T12:19:38Z | Merged: 2025-05-13T05:26:27Z

https://jira.corp.adobe.com/browse/SITES-30268


Diff:
diff --git a/src/metatags/handler.js b/src/metatags/handler.js
index a5d2765ad9..297a773ba9 100644
--- a/src/metatags/handler.js
+++ b/src/metatags/handler.js
@@ -31,11 +31,12 @@ import { syncSuggestions } from '../utils/data-access.js';
 import { createOpportunityData } from './opportunity-data-mapper.js';
 
 const auditType = Audit.AUDIT_TYPES.META_TAGS;
+const { AUDIT_STEP_DESTINATIONS } = Audit;
 
-export async function opportunityAndSuggestions(auditUrl, auditData, context) {
+export async function opportunityAndSuggestions(finalUrl, auditData, context) {
   const opportunity = await convertToOpportunity(
-    auditUrl,
-    auditData,
+    finalUrl,
+    { siteId: auditData.siteId, id: auditData.auditId },
     context,
     createOpportunityData,
     auditType,
@@ -191,8 +192,10 @@ async function calculateProjectedTraffic(context, auditUrl, siteId, detectedTags
   }
 }
 
-export async function auditMetaTagsRunner(auditUrl, context, site) {
-  const { log, s3Client, dataAccess } = context;
+export async function runAuditAndGenerateSuggestions(context) {
+  const {
+    site, audit, finalUrl, log, s3Client, dataAccess,
+  } = context;
   // Get top pages for a site
   const siteId = site.getId();
   const topPages = await getTopPagesForSiteId(dataAccess, siteId, context, log);
@@ -234,7 +237,7 @@ export async function auditMetaTagsRunner(auditUrl, context, site) {
     projectedTrafficValue,
   } = await calculateProjectedTraffic(
     context,
-    auditUrl,
+    finalUrl,
     siteId,
     detectedTags,
     log,
@@ -252,18 +255,54 @@ export async function auditMetaTagsRunner(auditUrl, context, site) {
     detectedTags: updatedDetectedTags,
     sourceS3Folder: `${bucketName}/${prefix}`,
     fullAuditRef: '',
-    finalUrl: auditUrl,
+    finalUrl,
     ...(projectedTrafficLost && { projectedTrafficLost }),
     ...(projectedTrafficValue && { projectedTrafficValue }),
   };
-  return {
+  await opportunityAndSuggestions(finalUrl, {
+    siteId: site.getId(),
+    auditId: audit.getId(),
     auditResult,
-    fullAuditRef: auditUrl,
+  }, context);
+
+  return {
+    status: 'complete',
+  };
+}
+
+export async function importTopPages(context) {
+  const { site, finalUrl } = context;
+
+  const s3BucketPath = `scrapes/${site.getId()}/`;
+  return {
+    type: 'top-pages',
+    siteId: site.getId(),
+    auditResult: { status: 'preparing', finalUrl },
+    fullAuditRef: s3BucketPath,
+  };
+}
+
+export async function submitForScraping(context) {
+  const {
+    site,
+    dataAccess,
+  } = context;
+  const { SiteTopPage } = dataAccess;
+  const topPages = await SiteTopPage.allBySiteIdAndSourceAndGeo(site.getId(), 'ahrefs', 'global');
+  if (topPages.length === 0) {
+    throw new Error('No top pages found for site');
+  }
+
+  return {
+    urls: topPages.map((topPage) => ({ url: topPage.getUrl() })),
+    siteId: site.getId(),
+    type: 'meta-tags',
   };
 }
 
 export default new AuditBuilder()
   .withUrlResolver(wwwUrlResolver)
-  .withRunner(auditMetaTagsRunner)
-  .withPostProcessors([opportunityAndSuggestions])
+  .addStep('submit-for-import-top-pages', importTopPages, AUDIT_STEP_DESTINATIONS.IMPORT_WORKER)
+  .addStep('submit-for-scraping', submitForScraping, AUDIT_STEP_DESTINATIONS.CONTENT_SCRAPER)
+  .addStep('run-audit-and-generate-suggestions', runAuditAndGenerateSuggestions)
   .build();
diff --git a/test/audits/metatags.test.js b/test/audits/metatags.test.js
index 2e967d2538..4f4e2fec0f 100644
--- a/test/audits/metatags.test.js
+++ b/test/audits/metatags.test.js
@@ -35,7 +35,12 @@ import {
 import SeoChecks from '../../src/metatags/seo-checks.js';
 import testData from '../fixtures/meta-tags-data.js';
 import { removeTrailingSlash } from '../../src/metatags/opportunity-utils.js';
-import { auditMetaTagsRunner, fetchAndProcessPageObject, opportunityAndSuggestions } from '../../src/metatags/handler.js';
+import {
+  importTopPages,
+  submitForScraping,
+  fetchAndProcessPageObject,
+  opportunityAndSuggestions,
+} from '../../src/metatags/handler.js';
 
 use(sinonChai);
 use(chaiAsPromised);
@@ -204,6 +209,9 @@ describe('Meta Tags', () => {
     let s3ClientStub;
     let logStub;
     let context;
+    let site;
+    let audit;
+
     beforeEach(() => {
       sinon.restore();
       dataAccessStub = {
@@ -214,7 +222,9 @@ describe('Meta Tags', () => {
           },
         },
         Configuration: {
-          findLatest: sinon.stub(),
+          findLatest: sinon.stub().resolves({
+            isHandlerEnabledForSite: sinon.stub().returns(true),
+          }),
         },
         Site: {
           findById: sinon.stub().resolves({ getIsLive: sinon.stub().returns(true) }),
@@ -223,6 +233,10 @@ describe('Meta Tags', () => {
           allBySiteId: sinon.stub(),
           allBySiteIdAndSourceAndGeo: sinon.stub(),
         },
+        Opportunity: {
+          allBySiteIdAndStatus: sinon.stub().resolves([]),
+          create: sinon.stub(),
+        },
       };
       s3ClientStub = {
         send: sinon.stub(),
@@ -234,9 +248,14 @@ describe('Meta Tags', () => {
         error: sinon.stub(),
         warn: sinon.stub(),
       };
-      const latestConfigStub = sinon.stub().resolves({
-        isHandlerEnabledForSite: () => false,
-      });
+      site = {
+        getId: sinon.stub().returns('site-id'),
+        getBaseURL: sinon.stub().returns('http://example.com'),
+        getIsLive: sinon.stub().returns(true),
+      };
+      audit = {
+        getId: sinon.stub().returns('audit-id'),
+      };
       context = {
         log: logStub,
         s3Client: s3ClientStub,
@@ -244,551 +263,479 @@ describe('Meta Tags', () => {
           S3_SCRAPER_BUCKET_NAME: 'test-bucket',
           GENVAR_ENDPOINT: 'test-genvar-url',
           FIREFALL_IMS_ORG_ID: 'test-org@adobe',
+          GENVAR_IMS_ORG_ID: 'test-org@adobe',
+          imsHost: 'https://ims-host.test',
+          clientId: 'test-client-id',
+          clientCode: 'test-client-code',
+          clientSecret: 'test-client-secret',
         },
-        dataAccess: {
-          Configuration: {
-            findLatest: latestConfigStub,
-          },
-          SiteTopPage: dataAccessStub.SiteTopPage,
-        },
+        dataAccess: dataAccessStub,
+        site,
+        finalUrl: 'http://example.com',
+        audit,
       };
     });
 
-    it('should process site tags and perform SEO checks', async () => {
-      const RUMAPIClientStub = {
-        createFrom: sinon.stub().returns({
-          query: sinon.stub().resolves([
-            {
-              url: 'http://example.com/blog/page1',
-              total: 100,
-              earned: 20,
-              owned: 70,
-              paid: 10,
-            },
-          ]),
-        }),
-      };
-      const mockGetRUMDomainkey = sinon.stub().resolves('mockedDomainKey');
-      const mockCalculateCPCValue = sinon.stub().resolves(2);
-      const metatagsOppty = {
-        getId: () => 'opportunity-id',
-        setAuditId: sinon.stub(),
-        save: sinon.stub(),
-        getSuggestions: sinon.stub().returns([]),
-        addSuggestions: sinon.stub().returns({ errorItems: [], createdItems: [1, 2, 3] }),
-        getType: () => 'meta-tags',
-      };
-      const site = {
-        getIsLive: sinon.stub().returns(true),
-        getId: sinon.stub().returns('site-id'),
-        getBaseURL: sinon.stub().returns('http://example.com'),
-      };
-      dataAccessStub.Site.findById.resolves(site);
-      dataAccessStub.Configuration.findLatest.resolves({
-        isHandlerEnabledForSite: sinon.stub().returns(true),
+    describe('importTopPages', () => {
+      it('should prepare import step with correct parameters', async () => {
+        const result = await importTopPages(context);
+        expect(result).to.deep.equal({
+          type: 'top-pages',
+          siteId: 'site-id',
+          auditResult: { status: 'preparing', finalUrl: 'http://example.com' },
+          fullAuditRef: 'scrapes/site-id/',
+        });
       });
-      const topPages = [
-        { getURL: 'http://example.com/blog/page1', getTopKeyword: sinon.stub().returns('page') },
-        { getURL: 'http://example.com/blog/page2', getTopKeyword: sinon.stub().returns('Test') },
-        { getURL: 'http://example.com/blog/page3', getTopKeyword: sinon.stub().returns('') },
-        { getURL: 'http://example.com/', getTopKeyword: sinon.stub().returns('Home') },
-      ];
-      dataAccessStub.SiteTopPage.allBySiteId.resolves(topPages);
-      dataAccessStub.Opportunity = {
-        allBySiteIdAndStatus: sinon.stub().returns([metatagsOppty]),
-      };
-      s3ClientStub.send
-        .withArgs(sinon.match.instanceOf(ListObjectsV2Command).and(sinon.match.has('input', {
-          Bucket: 'test-bucket',
-          Prefix: 'scrapes/site-id/',
-          MaxKeys: 1000,
-        })))
-        .resolves({
-          Contents: [
-            { Key: 'scrapes/site-id/blog/page1/scrape.json' },
-            { Key: 'scrapes/site-id/blog/page2/scrape.json' },
-            { Key: 'scrapes/site-id/blog/page3/scrape.json' },
-            { Key: 'scrapes/site-id/scrape.json' },
+    });
+
+    describe('submitForScraping', () => {
+      it('should submit top pages for scraping', async () => {
+        const topPages = [
+          { getUrl: () => 'http://example.com/page1' },
+          { getUrl: () => 'http://example.com/page2' },
+        ];
+        dataAccessStub.SiteTopPage.allBySiteIdAndSourceAndGeo.resolves(topPages);
+
+        const result = await submitForScraping(context);
+        expect(result).to.deep.equal({
+          urls: [
+            { url: 'http://example.com/page1' },
+            { url: 'http://example.com/page2' },
           ],
+          siteId: 'site-id',
+          type: 'meta-tags',
         });
+      });
+
+      it('should throw error if no top pages found', async () => {
+        dataAccessStub.SiteTopPage.allBySiteIdAndSourceAndGeo.resolves([]);
+        await expect(submitForScraping(context)).to.be.rejectedWith('No top pages found for site');
+      });
+    });
+
+    describe('fetchAndProcessPageObject', () => {
+      it('should process valid page object', async () => {
+        const mockScrapeResult = {
+          finalUrl: 'http://example.com/page1',
+          scrapeResult: {
+            tags: {
+              title: 'Test Page',
+              description: 'Test Description',
+              h1: ['Test H1'],
+            },
+          },
+        };
 
-      s3ClientStub.send
-        .withArgs(sinon.match.instanceOf(GetObjectCommand).and(sinon.match.has('input', {
-          Bucket: 'test-bucket',
-          Key: 'scrapes/site-id/blog/page1/scrape.json',
-        }))).returns({
+        s3ClientStub.send.resolves({
           Body: {
-            transformToString: () => JSON.stringify({
-              scrapeResult: {
-                tags: {
-                  title: 'Test Page',
-                  description: '',
-                },
-              },
-            }),
+            transformToString: () => JSON.stringify(mockScrapeResult),
           },
           ContentType: 'application/json',
         });
-      s3ClientStub.send
-        .withArgs(sinon.match.instanceOf(GetObjectCommand).and(sinon.match.has('input', {
-          Bucket: 'test-bucket',
-          Key: 'scrapes/site-id/blog/page2/scrape.json',
-        }))).returns({
-          Body: {
-            transformToString: () => JSON.stringify({
-              scrapeResult: {
-                tags: {
-                  title: 'Test Page',
-                  h1: [
-                    'This is a dummy H1 that is intentionally made to be overly lengthy from SEO perspective',
-                  ],
-                },
-              },
-            }),
+
+        const result = await fetchAndProcessPageObject(
+          s3ClientStub,
+          'test-bucket',
+          'scrapes/site-id/page1/scrape.json',
+          'scrapes/site-id/',
+          logStub,
+        );
+
+        expect(result).to.deep.equal({
+          '/page1': {
+            title: 'Test Page',
+            description: 'Test Description',
+            h1: ['Test H1'],
+            s3key: 'scrapes/site-id/page1/scrape.json',
           },
-          ContentType: 'application/json',
         });
-      s3ClientStub.send
-        .withArgs(sinon.match.instanceOf(GetObjectCommand).and(sinon.match.has('input', {
-          Bucket: 'test-bucket',
-          Key: 'scrapes/site-id/blog/page3/scrape.json',
-        }))).returns({
+      });
+
+      it('should handle empty pageUrl by converting it to root path', async () => {
+        const mockScrapeResult = {
+          finalUrl: '',
+          scrapeResult: {
+            tags: {
+              title: 'Home Page',
+              description: 'Home Description',
+              h1: ['Home H1'],
+            },
+          },
+        };
+
+        s3ClientStub.send.resolves({
           Body: {
-            transformToString: () => JSON.stringify({
-            }),
+            transformToString: () => JSON.stringify(mockScrapeResult),
           },
           ContentType: 'application/json',
         });
-      s3ClientStub.send
-        .withArgs(sinon.match.instanceOf(GetObjectCommand).and(sinon.match.has('input', {
-          Bucket: 'test-bucket',
-          Key: 'scrapes/site-id/scrape.json',
-        }))).returns({
+
+        const result = await fetchAndProcessPageObject(
+          s3ClientStub,
+          'test-bucket',
+          'scrapes/site-id/scrape.json',
+          'scrapes/site-id/',
+          logStub,
+        );
+
+        expect(result).to.deep.equal({
+          '/': {
+            title: 'Home Page',
+            description: 'Home Description',
+            h1: ['Home H1'],
+            s3key: 'scrapes/site-id/scrape.json',
+          },
+        });
+      });
+
+      it('should handle missing tags', async () => {
+        s3ClientStub.send.resolves({
           Body: {
-            transformToString: () => JSON.stringify({
-              finalUrl: 'http://example.com/site-id/',
-              scrapeResult: {
-                tags: {
-                  title: 'Home Page',
-                  description: 'Home page description',
-                },
-              },
-            }),
+            transformToString: () => JSON.stringify({}),
           },
           ContentType: 'application/json',
         });
 
-      const addAuditStub = sinon.stub().resolves({ getId: () => 'audit-id' });
-      dataAccessStub.Audit.create = await addAuditStub();
-      const auditStub = await esmock('../../src/metatags/handler.js', {
-        '../../src/support/utils.js': { getRUMDomainkey: mockGetRUMDomainkey, calculateCPCValue: mockCalculateCPCValue },
-        '@adobe/spacecat-shared-rum-api-client': RUMAPIClientStub,
-        '@adobe/spacecat-shared-data-access': dataAccessStub,
-      });
-      const auditInstance = auditStub.default;
-      await auditInstance.runner('http://example.com', context, site);
+        const result = await fetchAndProcessPageObject(
+          s3ClientStub,
+          'test-bucket',
+          'scrapes/site-id/page1/scrape.json',
+          'scrapes/site-id/',
+          logStub,
+        );
 
-      const result = await fetchAndProcessPageObject(s3ClientStub, 'test-bucket', 'scrapes/site-id/blog/page3/scrape.json', 'scrapes/site-id/', logStub);
-      expect(logStub.error).to.have.been.calledWith('No Scraped tags found in S3 scrapes/site-id/blog/page3/scrape.json object');
-      expect(result).to.be.null;
+        expect(result).to.be.null;
+        expect(logStub.error).to.have.been.calledWith(
+          'No Scraped tags found in S3 scrapes/site-id/page1/scrape.json object',
+        );
+      });
+    });
 
-      expect(addAuditStub.calledWithMatch({
-        '/blog/page1': {
-          h1: {
-            seoImpact: 'High',
-            issue: 'Missing H1',
-            issueDetails: 'H1 tag is missing',
-            seoRecommendation: 'Should be present',
-          },
-          title: {
-            tagContent: 'Test Page',
-            seoImpact: 'High',
-            issue: 'Duplicate Title',
-            issueDetails: '2 pages share same title',
-            seoRecommendation: 'Unique across pages',
-          },
-          description: {
-            tagContent: '',
-            seoImpact: 'High',
-            issue: 'Empty Description',
-            issueDetails: 'Description tag is empty',
-            seoRecommendation: '140-160 characters long',
-          },
-        },
-        '/blog/page2': {
-          description: {
-            seoImpact: 'High',
-            issue: 'Missing Description',
-            issueDetails: 'Description tag is missing',
-            seoRecommendation: 'Should be present',
-          },
-          title: {
-            tagContent: 'Test Page',
-            seoImpact: 'High',
-            issue: 'Duplicate Title',
-            issueDetails: '2 pages share same title',
-            seoRecommendation: 'Unique across pages',
-          },
-          h1: {
-            tagContent: 'This is a dummy H1 that is intentionally made to be overly lengthy from SEO perspective',
-            seoImpact: 'Moderate',
-            issue: 'H1 too long',
-            issueDetails: '17 chars over limit',
-            seoRecommendation: 'Below 70 characters',
+    describe('opportunities handler method', () => {
+      let auditData;
+      let auditUrl;
+      let opportunity;
+
+      beforeEach(() => {
+        sinon.restore();
+        auditUrl = 'https://example.com';
+        opportunity = {
+          getId: () => 'opportunity-id',
+          setAuditId: sinon.stub(),
+          save: sinon.stub(),
+          getSuggestions: sinon.stub().returns(testData.existingSuggestions),
+          addSuggestions: sinon.stub().returns({ errorItems: [], createdItems: [1, 2, 3] }),
+          getType: () => 'meta-tags',
+          setData: () => {},
+          getData: () => {},
+        };
+        logStub = {
+          info: sinon.stub(),
+          debug: sinon.stub(),
+          error: sinon.stub(),
+        };
+        dataAccessStub = {
+          Opportunity: {
+            allBySiteIdAndStatus: sinon.stub().resolves([]),
+            create: sinon.stub(),
           },
-        },
-        '/': {
-          title: {
-            tagContent: 'Home Page',
-            seoImpact: 'High',
-            issue: 'Duplicate Title',
-            issueDetails: '2 pages share same title',
-            seoRecommendation: 'Unique across pages',
+          Suggestion: {
+            bulkUpdateStatus: sinon.stub(),
           },
-          description: {
-            tagContent: 'Home page description',
-            seoImpact: 'High',
-            issue: 'Duplicate Description',
-            issueDetails: '2 pages share same description',
-            seoRecommendation: 'Unique across pages',
+        };
+        context = {
+          log: logStub,
+          dataAccess: dataAccessStub,
+          env: {
+            S3_SCRAPER_BUCKET_NAME: 'test-bucket',
           },
-        },
-      }));
-      expect(addAuditStub.calledOnce).to.be.true;
-      expect(logStub.info.callCount).to.equal(6);
-    });
+        };
+        auditData = testData.auditData;
+      });
 
-    it('should process site tags and perform SEO checks for pages with invalid H1s', async () => {
-      const RUMAPIClientStub = {
-        createFrom: sinon.stub().returns({
-          query: sinon.stub().resolves([
-            {
-              url: 'http://example.com/blog/page1',
-              total: 100,
-              earned: 20,
-              owned: 70,
-              paid: 10,
-            },
-          ]),
-        }),
-      };
-      const mockGetRUMDomainkey = sinon.stub().resolves('mockedDomainKey');
-      const mockCalculateCPCValue = sinon.stub().resolves(2);
-      const auditStub = await esmock('../../src/metatags/handler.js', {
-        '../../src/support/utils.js': { getRUMDomainkey: mockGetRUMDomainkey, calculateCPCValue: mockCalculateCPCValue },
-        '@adobe/spacecat-shared-rum-api-client': RUMAPIClientStub,
+      it('should create new opportunity and add suggestions', async () => {
+        opportunity.getType = () => 'meta-tags';
+        dataAccessStub.Opportunity.create = sinon.stub().returns(opportunity);
+        await opportunityAndSuggestions(auditUrl, auditData, context);
+        expect(dataAccessStub.Opportunity.create).to.be.calledWith(testData.OpportunityData);
+        expect(logStub.info).to.be.calledWith('Successfully synced Opportunity And Suggestions for site: site-id and meta-tags audit type.');
       });
-      const site = {
-        getIsLive: sinon.stub().returns(true),
-        getId: sinon.stub().returns('site-id'),
-        getBaseURL: sinon.stub().returns('http://example.com'),
-      };
-      const topPages = [{ getURL: 'http://example.com/blog/page1', getTopKeyword: sinon.stub().returns('page') },
-        { getURL: 'http://example.com/blog/page2', getTopKeyword: sinon.stub().returns('Test') },
-        { getURL: 'http://example.com/', getTopKeyword: sinon.stub().returns('Test') }];
 
-      dataAccessStub.Site.findById.resolves(site);
-      dataAccessStub.Configuration.findLatest.resolves({
-        isHandlerEnabledForSite: sinon.stub().returns(true),
+      it('should use existing opportunity and add suggestions', async () => {
+        dataAccessStub.Opportunity.allBySiteIdAndStatus.resolves([opportunity]);
+        await opportunityAndSuggestions(auditUrl, auditData, context);
+        expect(opportunity.save).to.be.calledOnce;
+        expect(logStub.info).to.be.calledWith('Successfully synced Opportunity And Suggestions for site: site-id and meta-tags audit type.');
       });
-      const getTopPagesForSiteStub = [
-        { getUrl: () => 'http://example.com/blog/page1' },
-        {
-          getUrl: () => 'http://example.com/blog/page2',
-        },
-        {
-          getUrl: () => 'http://example.com/',
-        },
-      ];
-      dataAccessStub.SiteTopPage.allBySiteId.resolves(topPages);
-      dataAccessStub.SiteTopPage.allBySiteIdAndSourceAndGeo.resolves(getTopPagesForSiteStub);
-
-      s3ClientStub.send
-        .withArgs(sinon.match.instanceOf(ListObjectsV2Command).and(sinon.match.has('input', {
-          Bucket: 'test-bucket',
-          Prefix: 'scrapes/site-id/',
-          MaxKeys: 1000,
-        })))
-        .resolves({
-          Contents: [
-            { Key: 'scrapes/site-id/blog/page1/scrape.json' },
-            { Key: 'scrapes/site-id/blog/page2/scrape.json' },
-            { Key: 'scrapes/site-id/scrape.json' },
-          ],
-        });
 
-      s3ClientStub.send
-        .withArgs(sinon.match.instanceOf(GetObjectCommand).and(sinon.match.has('input', {
-          Bucket: 'test-bucket',
-          Key: 'scrapes/site-id/blog/page1/scrape.json',
-        }))).returns({
-          Body: {
-            transformToString: () => JSON.stringify({
-              finalUrl: 'https://example.com/blog/page1/',
-              scrapeResult: {
-                tags: {
-                  title: 'This is an SEO optimal page1 valid title.',
-                  description: 'This is a dummy description that is optimal from SEO perspective for page1. It has the correct length of characters, and is unique across all pages.',
-                  h1: [
-                    'This is an overly long H1 tag from SEO perspective due to its length exceeding 60 chars',
-                    'This is second h1 tag on same page',
+      it('should throw error if fetching opportunity fails', async () => {
+        dataAccessStub.Opportunity.allBySiteIdAndStatus.rejects(new Error('some-error'));
+        try {
+          await opportunityAndSuggestions(auditUrl, auditData, context);
+        } catch (err) {
+          expect(err.message).to.equal('Failed to fetch opportunities for siteId site-id: some-error');
+        }
+        expect(logStub.error).to.be.calledWith('Fetching opportunities for siteId site-id failed with error: some-error');
+      });
+
+      it('should throw error if creating opportunity fails', async () => {
+        dataAccessStub.Opportunity.allBySiteIdAndStatus.returns([]);
+        dataAccessStub.Opportunity.create = sinon.stub().rejects(new Error('some-error'));
+        try {
+          await opportunityAndSuggestions(auditUrl, auditData, context);
+        } catch (err) {
+          expect(err.message).to.equal('some-error');
+        }
+        expect(logStub.error).to.be.calledWith('Failed to create new opportunity for siteId site-id and auditId audit-id: some-error');
+      });
+
+      it('should sync existing suggestions with new suggestions', async () => {
+        dataAccessStub.Opportunity.allBySiteIdAndStatus.resolves([opportunity]);
+        opportunity.getSuggestions.returns(testData.existingSuggestions);
+        await opportunityAndSuggestions(auditUrl, auditData, context);
+        expect(opportunity.save).to.be.calledOnce;
+        expect(logStub.info).to.be.calledWith('Successfully synced Opportunity And Suggestions for site: site-id and meta-tags audit type.');
+      });
+
+      it('should mark existing suggestions OUTDATED if not present in audit data', async () => {
+        dataAccessStub.Opportunity.allBySiteIdAndStatus.resolves([opportunity]);
+        opportunity.getSuggestions.returns(testData.existingSuggestions);
+        const auditDataModified = {
+          type: 'meta-tags',
+          siteId: 'site-id',
+          id: 'audit-id',
+          auditResult: {
+            finalUrl: 'www.test-site.com/',
+            detectedTags: {
+              '/page1': {
+                title: {
+                  tagContent: 'Lovesac - 404 Not Found',
+                  duplicates: [
+                    '/page4',
+                    '/page5',
                   ],
+                  seoRecommendation: 'Unique across pages',
+                  issue: 'Duplicate Title',
+                  issueDetails: '3 pages share same title',
+                  seoImpact: 'High',
                 },
-              },
-            }),
-          },
-          ContentType: 'application/json',
-        });
-      s3ClientStub.send
-        .withArgs(sinon.match.instanceOf(GetObjectCommand).and(sinon.match.has('input', {
-          Bucket: 'test-bucket',
-          Key: 'scrapes/site-id/blog/page2/scrape.json',
-        }))).returns({
-          Body: {
-            transformToString: () => JSON.stringify({
-              scrapeResult: {
-                tags: {
-                  title: 'This is a SEO wise optimised page2 title.',
-                  description: 'This is a dummy description that is optimal from SEO perspective for page2. It has the correct length of characters, and is unique across all pages.',
-                  h1: [
-                    'This is also an overly long H1 tag from SEO perspective due to its length exceeding 60 chars',
-                  ],
+                h1: {
+                  seoRecommendation: 'Should be present',
+                  issue: 'Missing H1',
+                  issueDetails: 'H1 tag is missing',
+                  seoImpact: 'High',
                 },
               },
-            }),
-          },
-          ContentType: 'application/json',
-        });
-      s3ClientStub.send
-        .withArgs(sinon.match.instanceOf(GetObjectCommand).and(sinon.match.has('input', {
-          Bucket: 'test-bucket',
-          Key: 'scrapes/site-id/scrape.json',
-        }))).returns({
-          Body: {
-            transformToString: () => JSON.stringify({
-              scrapeResult: {
-                tags: {
-                  title: 'This is an SEO optimal page1 valid title.',
-                  description: 'This is a dummy description that is optimal from SEO perspective for page1. It has the correct length of characters, and is unique across all pages.',
-                  h1: undefined,
+              '/page2': {
+                title: {
+                  seoRecommendation: '40-60 characters long',
+                  issue: 'Empty Title',
+                  issueDetails: 'Title tag is empty',
+                  seoImpact: 'High',
+                },
+                h1: {
+                  tagContent: '["We Can All Win Together","We Say As We Do"]',
+                  seoRecommendation: '1 H1 on a page',
+                  issue: 'Multiple H1 on page',
+                  issueDetails: '2 H1 detected',
+                  seoImpact: 'Moderate',
                 },
               },
-            }),
-          },
-          ContentType: 'application/json',
-        });
-      const addAuditStub = sinon.stub().resolves();
-      dataAccessStub.Audit.create = await addAuditStub();
-
-      const auditInstance = auditStub.default;
-      await auditInstance.runner('http://example.com', context, site);
-      await fetchAndProcessPageObject(s3ClientStub, 'test-bucket', 'scrapes/site-id/blog/page1/scrape.json', 'scrapes/site-id/', sinon.stub());
-
-      expect(addAuditStub.calledWithMatch({
-        '/blog/page1': {
-          h1: {
-            tagContent: '["This is an overly long H1 tag from SEO perspective due to its length exceeding 60 chars","This is second h1 tag on same page"]',
-            seoImpact: 'Moderate',
-            issue: 'Multiple H1 on page',
-            issueDetails: '2 H1 detected',
-            seoRecommendation: '1 H1 on a page',
-          },
-          title: {
-            tagContent: 'This is an SEO optimal page1 valid title.',
-            seoImpact: 'High',
-            issue: 'Duplicate Title',
-            issueDetails: '2 pages share same title',
-            seoRecommendation: 'Unique across pages',
-          },
-          description: {
-            tagContent: 'This is a dummy description that is optimal from SEO perspective for page1. It has the correct length of characters, and is unique across all pages.',
-            seoImpact: 'High',
-            issue: 'Duplicate Description',
-            issueDetails: '2 pages share same description',
-            seoRecommendation: 'Unique across pages',
-          },
-        },
-        '/blog/page2': {
-          h1: {
-            tagContent: 'This is also an overly long H1 tag from SEO perspective due to its length exceeding 60 chars',
-            seoImpact: 'Moderate',
-            issue: 'H1 too long',
-            issueDetails: '22 chars over limit',
-            seoRecommendation: 'Below 70 characters',
-          },
-        },
-        '/': {
-          h1: {
-            seoImpact: 'High',
-            issue: 'Missing H1',
-            issueDetails: 'H1 tag is missing',
-            seoRecommendation: 'Should be present',
-          },
-          title: {
-            tagContent: 'This is an SEO optimal page1 valid title.',
-            seoImpact: 'High',
-            issue: 'Duplicate Title',
-            issueDetails: '2 pages share same title',
-            seoRecommendation: 'Unique across pages',
+            },
           },
-          description: {
-            tagContent: 'This is a dummy description that is optimal from SEO perspective for page1. It has the correct length of characters, and is unique across all pages.',
+        };
+        await opportunityAndSuggestions(auditUrl, auditDataModified, context);
+        expect(dataAccessStub.Suggestion.bulkUpdateStatus).to.be.calledWith(testData.existingSuggestions.splice(0, 2), 'OUTDATED');
+        expect(opportunity.save).to.be.calledOnce;
+        expect(logStub.info).to.be.calledWith('Successfully synced Opportunity And Suggestions for site: site-id and meta-tags audit type.');
+      });
+
+      it('should preserve existing AI suggestions and overrides when syncing', async () => {
+        dataAccessStub.Opportunity.allBySiteIdAndStatus.resolves([opportunity]);
+
+        // Setup existing suggestion with AI data and overrides
+        const existingSuggestion = {
+          getData: () => ({
+            url: 'https://example.com/page1',
+            tagName: 'title',
+            tagContent: 'Original Title',
+            issue: 'Title too short',
             seoImpact: 'High',
-            issue: 'Duplicate Description',
-            issueDetails: '2 pages share same description',
-            seoRecommendation: 'Unique across pages',
+            aiSuggestion: 'AI Generated Title',
+            aiRationale: 'AI explanation for the title',
+            toOverride: true,
+          }),
+          getStatus: () => 'pending',
+          remove: sinon.stub(),
+          setData: sinon.stub(),
+          save: sinon.stub(),
+        };
+
+        opportunity.getSuggestions.returns([existingSuggestion]);
+
+        // Create audit data with different content for same URL
+        const modifiedAuditData = {
+          siteId: 'site-id',
+          auditId: 'audit-id',
+          auditResult: {
+            finalUrl: 'https://example.com',
+            detectedTags: {
+              '/page1': {
+                title: {
+                  tagContent: 'Original Title',
+                  issue: 'Title too short',
+                  seoImpact: 'High',
+                },
+              },
+            },
           },
-        },
-      }));
-      expect(addAuditStub.calledOnce).to.be.true;
-      expect(logStub.info.callCount).to.equal(7);
-    });
+        };
 
-    it('should handle gracefully if S3 object has no rawbody', async () => {
-      const topPages = [{ getURL: 'http://example.com/blog/page1', getTopKeyword: sinon.stub().returns('page') }];
+        await opportunityAndSuggestions(auditUrl, modifiedAuditData, context);
 
-      dataAccessStub.SiteTopPage.allBySiteId.resolves(topPages);
+        // Verify that existing suggestion was updated properly
+        expect(opportunity.save).to.be.calledOnce;
+        expect(existingSuggestion.setData).to.be.calledOnce;
 
-      s3ClientStub.send
-        .withArgs(sinon.match.instanceOf(ListObjectsV2Command).and(sinon.match.has('input', {
-          Bucket: 'test-bucket',
-          Prefix: 'scrapes/site-id/',
-          MaxKeys: 1000,
-        })))
-        .resolves({
-          Contents: [
-            { Key: 'scrapes/site-id/blog/page1/scrape.json' },
-          ],
-        });
+        const setDataCall = existingSuggestion.setData.getCall(0);
+        const updatedData = setDataCall.args[0];
 
-      s3ClientStub.send
-        .withArgs(sinon.match.instanceOf(GetObjectCommand).and(sinon.match.has('input', {
-          Bucket: 'test-bucket',
-          Key: 'scrapes/site-id/blog/page1/scrape.json',
-        }))).returns({
-          Body: {
-            transformToString: () => '',
-          },
-          ContentType: 'application/json',
+        // Verify the original AI data and override flags were preserved
+        expect(updatedData).to.deep.include({
+          aiSuggestion: 'AI Generated Title',
+          aiRationale: 'AI explanation for the title',
+          toOverride: true,
         });
-      const addAuditStub = sinon.stub().resolves();
-      dataAccessStub.addAudit = addAuditStub;
 
-      auditMetaTagsRunner('http://example.com', { log: sinon.stub(), s3Client: s3ClientStub }, { getId: () => 'site-id' });
-      fetchAndProcessPageObject(s3ClientStub, 'test-bucket', 'scrapes/site-id/blog/page1/scrape.json', 'scrapes/site-id/', sinon.stub());
+        // Verify the suggestion was saved
+        expect(existingSuggestion.save).to.be.calledOnce;
+      });
 
-      expect(addAuditStub.calledOnce).to.be.false;
-    });
+      it('should throw error if suggestions fail to create', async () => {
+        sinon.stub(GoogleClient, 'createFrom').resolves({});
+        dataAccessStub.Opportunity.allBySiteIdAndStatus.resolves([opportunity]);
+        opportunity.getSiteId = () => 'site-id';
+        opportunity.addSuggestions = sinon.stub().returns({ errorItems: [{ item: 1, error: 'some-error' }], createdItems: [] });
+        try {
+          await opportunityAndSuggestions(auditUrl, auditData, context);
+        } catch (err) {
+          expect(err.message).to.equal('Failed to create suggestions for siteId site-id');
+        }
+        expect(opportunity.save).to.be.calledOnce;
+        expect(logStub.error).to.be.calledWith('Suggestions for siteId site-id contains 1 items with errors');
+        expect(logStub.error).to.be.calledTwice;
+      });
 
-    it('should handle gracefully if S3 tags object is not valid', async () => {
-      const topPages = [{ getURL: 'http://example.com/blog/page1', getTopKeyword: sinon.stub().returns('page') },
-        { getURL: 'http://example.com/blog/page2', getTopKeyword: sinon.stub().returns('Test') }];
+      it('should take rank as -1 if issue is not known', async () => {
+        dataAccessStub.Opportunity.allBySiteIdAndStatus.resolves([opportunity]);
+        const auditDataModified = {
+          ...testData.auditData,
+        };
+        auditDataModified.auditResult.detectedTags['/page1'].title.issue = 'some random issue';
+        const expectedSuggestionModified = [
+          ...testData.expectedSuggestions,
+        ];
+        expectedSuggestionModified[0].data.issue = 'some random issue';
+        expectedSuggestionModified[0].data.rank = -1;
+        expectedSuggestionModified[0].rank = -1;
+        await opportunityAndSuggestions(auditUrl, auditData, context);
+        expect(opportunity.save).to.be.calledOnce;
+        expect(logStub.info).to.be.calledWith('Successfully synced Opportunity And Suggestions for site: site-id and meta-tags audit type.');
+      });
+    });
 
-      dataAccessStub.SiteTopPage.allBySiteId.resolves(topPages);
+    describe('runAuditAndGenerateSuggestions', () => {
+      let RUMAPIClientStub;
+      let metatagsOppty;
 
-      s3ClientStub.send
-        .withArgs(sinon.match.instanceOf(ListObjectsV2Command).and(sinon.match.has('input', {
-          Bucket: 'test-bucket',
-          Prefix: 'scrapes/site-id/',
-          MaxKeys: 1000,
-        })))
-        .resolves({
-          Contents: [
-            { Key: 'page1/scrape.json' },
-          ],
-        });
+      beforeEach(() => {
+        s3ClientStub = {
+          send: sinon.stub(),
+          getObject: sinon.stub(),
+        };
+        logStub = {
+          info: sinon.stub(),
+          debug: sinon.stub(),
+          error: sinon.stub(),
+          warn: sinon.stub(),
+        };
+        dataAccessStub = {
+          SiteTopPage: {
+            allBySiteId: sinon.stub(),
+            allBySiteIdAndSourceAndGeo: sinon.stub(),
+          },
+          Site: {
+            findById: sinon.stub(),
+          },
+          Configuration: {
+            findLatest: sinon.stub(),
+          },
+          Opportunity: {
+            allBySiteIdAndStatus: sinon.stub(),
+          },
+        };
 
-      s3ClientStub.send
-        .withArgs(sinon.match.instanceOf(GetObjectCommand))
-        .returns({
-          Body: {
-            transformToString: () => JSON.stringify({
-              scrapeResult: {
-                tags: 5,
+        RUMAPIClientStub = {
+          createFrom: sinon.stub().returns({
+            query: sinon.stub().resolves([
+              {
+                url: 'http://example.com/blog/page1',
+                total: 100,
+                earned: 20,
+                owned: 70,
+                paid: 10,
               },
-            }),
-          },
-          ContentType: 'application/json',
-        });
+            ]),
+          }),
+        };
 
-      const site = {
-        getIsLive: sinon.stub().returns(true),
-        getId: sinon.stub().returns('site-id'),
-        getBaseURL: sinon.stub().returns('http://example.com'),
-      };
-      const RUMAPIClientStub = {
-        createFrom: sinon.stub().returns({
-          query: sinon.stub().resolves([
-            {
-              url: 'http://example.com/blog/page1',
-              total: 100,
-              earned: 20,
-              owned: 70,
-              paid: 10,
-            },
-          ]),
-        }),
-      };
-      const mockGetRUMDomainkey = sinon.stub().resolves('mockedDomainKey');
-      const mockCalculateCPCValue = sinon.stub().resolves(2);
-      const auditStub = await esmock('../../src/metatags/handler.js', {
-        '../../src/support/utils.js': { getRUMDomainkey: mockGetRUMDomainkey, calculateCPCValue: mockCalculateCPCValue },
-        '@adobe/spacecat-shared-rum-api-client': RUMAPIClientStub,
-      });
-      const auditInstance = auditStub.default;
-      await auditInstance.runner('http://example.com', context, site);
-      expect(logStub.error).to.have.been.calledWith('Failed to extract tags from scraped content for bucket test-bucket and prefix scrapes/site-id/');
-    });
+        metatagsOppty = {
+          getId: () => 'opportunity-id',
+          setAuditId: sinon.stub(),
+          save: sinon.stub(),
+          getSuggestions: sinon.stub().returns([]),
+          addSuggestions: sinon.stub().returns({ errorItems: [], createdItems: [1, 2, 3] }),
+          getType: () => 'meta-tags',
+          setData: sinon.stub(),
+          getData: sinon.stub(),
+        };
 
-    it('should handle gracefully if Rum throws error', async () => {
-      const RUMAPIClientStub = {
-        createFrom: sinon.stub().returns({
-          query: sinon.stub().throws(new Error('RUM not available')),
-        }),
-      };
-      const mockGetRUMDomainkey = sinon.stub().resolves('mockedDomainKey');
-      const metatagsOppty = {
-        getId: () => 'opportunity-id',
-        setAuditId: sinon.stub(),
-        save: sinon.stub(),
-        getSuggestions: sinon.stub().returns([]),
-        addSuggestions: sinon.stub().returns({ errorItems: [], createdItems: [1, 2, 3] }),
-        getType: () => 'meta-tags',
-      };
-      const site = {
-        getIsLive: sinon.stub().returns(true),
-        getId: sinon.stub().returns('site-id'),
-        getBaseURL: sinon.stub().returns('http://example.com'),
-      };
-      const topPages = [{ getURL: 'http://example.com/blog/page1', getTopKeyword: sinon.stub().returns('page') },
-        { getURL: 'http://example.com/blog/page2', getTopKeyword: sinon.stub().returns('Test') }];
+        site = {
+          getIsLive: sinon.stub().returns(true),
+          getId: sinon.stub().returns('site-id'),
+          getBaseURL: sinon.stub().returns('http://example.com'),
+        };
 
-      dataAccessStub.Site.findById.resolves(site);
-      dataAccessStub.Configuration.findLatest.resolves({
-        isHandlerEnabledForSite: sinon.stub().returns(true),
-      });
-      dataAccessStub.SiteTopPage.allBySiteId.resolves(topPages);
-      dataAccessStub.Opportunity = {
-        allBySiteIdAndStatus: sinon.stub().returns([metatagsOppty]),
-      };
-      s3ClientStub.send
-        .withArgs(sinon.match.instanceOf(ListObjectsV2Command).and(sinon.match.has('input', {
-          Bucket: 'test-bucket',
-          Prefix: 'scrapes/site-id/',
-          MaxKeys: 1000,
-        })))
-        .resolves({
+        audit = {
+          getId: sinon.stub().returns('audit-id'),
+        };
+
+        dataAccessStub.Site.findById.resolves(site);
+        dataAccessStub.Configuration.findLatest.resolves({
+          isHandlerEnabledForSite: sinon.stub().returns(true),
+        });
+
+        const topPages = [
+          { getUrl: () => 'http://example.com/blog/page1', getTopKeyword: sinon.stub().returns('page') },
+          { getUrl: () => 'http://example.com/blog/page2', getTopKeyword: sinon.stub().returns('Test') },
+          { getUrl: () => 'http://example.com/blog/page3', getTopKeyword: sinon.stub().returns('') },
+          { getUrl: () => 'http://example.com/', getTopKeyword: sinon.stub().returns('Home') },
+        ];
+        dataAccessStub.SiteTopPage.allBySiteId.resolves(topPages);
+        dataAccessStub.SiteTopPage.allBySiteIdAndSourceAndGeo.resolves(topPages);
+        dataAccessStub.Opportunity.allBySiteIdAndStatus.returns([metatagsOppty]);
+
+        // Setup S3 client stubs
+        const listObjectsResponse = {
           Contents: [
             { Key: 'scrapes/site-id/blog/page1/scrape.json' },
             { Key: 'scrapes/site-id/blog/page2/scrape.json' },
+            { Key: 'scrapes/site-id/blog/page3/scrape.json' },
+            { Key: 'scrapes/site-id/scrape.json' },
           ],
-        });
+        };
 
-      s3ClientStub.send
-        .withArgs(sinon.match.instanceOf(GetObjectCommand).and(sinon.match.has('input', {
-          Bucket: 'test-bucket',
-          Key: 'scrapes/site-id/blog/page1/scrape.json',
-        }))).returns({
+        const page1Response = {
           Body: {
             transformToString: () => JSON.stringify({
               scrapeResult: {
@@ -800,12 +747,9 @@ describe('Meta Tags', () => {
             }),
           },
           ContentType: 'application/json',
-        });
-      s3ClientStub.send
-        .withArgs(sinon.match.instanceOf(GetObjectCommand).and(sinon.match.has('input', {
-          Bucket: 'test-bucket',
-          Key: 'scrapes/site-id/blog/page2/scrape.json',
-        }))).returns({
+        };
+
+        const page2Response = {
           Body: {
             transformToString: () => JSON.stringify({
               scrapeResult: {
@@ -819,687 +763,290 @@ describe('Meta Tags', () => {
             }),
           },
           ContentType: 'application/json',
-        });
-      const addAuditStub = sinon.stub().resolves({ getId: () => 'audit-id' });
-      dataAccessStub.Audit.create = addAuditStub;
-      const mockCalculateCPCValue = sinon.stub().resolves(2);
-      const auditStub = await esmock('../../src/metatags/handler.js', {
-        '../../src/support/utils.js': { getRUMDomainkey: mockGetRUMDomainkey, calculateCPCValue: mockCalculateCPCValue },
-        '@adobe/spacecat-shared-rum-api-client': RUMAPIClientStub,
-      });
-      const auditInstance = auditStub.default;
-      await auditInstance.runner('http://example.com', context, site);
-      expect(addAuditStub.calledWithMatch({
-        auditResult: {
-          detectedTags: {
-            '/blog/page1': {
-              h1: {
-                seoImpact: 'High',
-                issue: 'Missing H1',
-                issueDetails: 'H1 tag is missing',
-                seoRecommendation: 'Should be present',
-              },
-              title: {
-                tagContent: 'Test Page',
-                seoImpact: 'High',
-                issue: 'Duplicate Title',
-                issueDetails: '2 pages share same title',
-                seoRecommendation: 'Unique across pages',
-                duplicates: [
-                  '/blog/page2',
-                ],
-              },
-              description: {
-                tagContent: '',
-                seoImpact: 'High',
-                issue: 'Empty Description',
-                issueDetails: 'Description tag is empty',
-                seoRecommendation: '140-160 characters long',
-              },
-            },
-            '/blog/page2': {
-              description: {
-                seoImpact: 'High',
-                issue: 'Missing Description',
-                issueDetails: 'Description tag is missing',
-                seoRecommendation: 'Should be present',
-              },
-              title: {
-                tagContent: 'Test Page',
-                seoImpact: 'High',
-                issue: 'Duplicate Title',
-                issueDetails: '2 pages share same title',
-                seoRecommendation: 'Unique across pages',
-                duplicates: [
-                  '/blog/page1',
-                ],
-              },
-              h1: {
-                tagContent: 'This is a dummy H1 that is intentionally made to be overly lengthy from SEO perspective',
-                seoImpact: 'Moderate',
-                issue: 'H1 too long',
-                issueDetails: '17 chars over limit',
-                seoRecommendation: 'Below 70 characters',
-              },
-            },
-          },
-        },
-      }));
-    });
-
-    it('should skip updating projected traffic if value less than 500', async () => {
-      const RUMAPIClientStub = {
-        createFrom: sinon.stub().returns({
-          query: sinon.stub().resolves([
-            {
-              url: 'http://example.com/blog/page1',
-              total: 100,
-              earned: 20,
-              owned: 70,
-              paid: 10,
-            },
-          ]),
-        }),
-      };
-      const mockGetRUMDomainkey = sinon.stub().resolves('mockedDomainKey');
-      const metatagsOppty = {
-        getId: () => 'opportunity-id',
-        setAuditId: sinon.stub(),
-        save: sinon.stub(),
-        getSuggestions: sinon.stub().returns([]),
-        addSuggestions: sinon.stub().returns({ errorItems: [], createdItems: [1, 2, 3] }),
-        getType: () => 'meta-tags',
-      };
-      const site = {
-        getIsLive: sinon.stub().returns(true),
-        getId: sinon.stub().returns('site-id'),
-        getBaseURL: sinon.stub().returns('http://example.com'),
-      };
-      const topPages = [{ getURL: 'http://example.com/blog/page1', getTopKeyword: sinon.stub().returns('page') },
-        { getURL: 'http://example.com/blog/page2', getTopKeyword: sinon.stub().returns('Test') }];
-
-      dataAccessStub.Site.findById.resolves(site);
-      dataAccessStub.Configuration.findLatest.resolves({
-        isHandlerEnabledForSite: sinon.stub().returns(true),
-      });
-      dataAccessStub.SiteTopPage.allBySiteId.resolves(topPages);
-      dataAccessStub.Opportunity = {
-        allBySiteIdAndStatus: sinon.stub().returns([metatagsOppty]),
-      };
-      s3ClientStub.send
-        .withArgs(sinon.match.instanceOf(ListObjectsV2Command).and(sinon.match.has('input', {
-          Bucket: 'test-bucket',
-          Prefix: 'scrapes/site-id/',
-          MaxKeys: 1000,
-        })))
-        .resolves({
-          Contents: [
-            { Key: 'scrapes/site-id/blog/page1/scrape.json' },
-            { Key: 'scrapes/site-id/blog/page2/scrape.json' },
-          ],
-        });
+        };
 
-      s3ClientStub.send
-        .withArgs(sinon.match.instanceOf(GetObjectCommand).and(sinon.match.has('input', {
-          Bucket: 'test-bucket',
-          Key: 'scrapes/site-id/blog/page1/scrape.json',
-        }))).returns({
+        const page3Response = {
           Body: {
             transformToString: () => JSON.stringify({
-              scrapeResult: {
-                tags: {
-                  title: 'Test Page',
-                  description: '',
-                },
-              },
             }),
           },
           ContentType: 'application/json',
-        });
-      s3ClientStub.send
-        .withArgs(sinon.match.instanceOf(GetObjectCommand).and(sinon.match.has('input', {
-          Bucket: 'test-bucket',
-          Key: 'scrapes/site-id/blog/page2/scrape.json',
-        }))).returns({
+        };
+
+        const homePageResponse = {
           Body: {
             transformToString: () => JSON.stringify({
+              finalUrl: 'http://example.com/site-id/',
               scrapeResult: {
                 tags: {
-                  title: 'Test Page',
-                  h1: [
-                    'This is a dummy H1 that is intentionally made to be overly lengthy from SEO perspective',
-                  ],
+                  title: 'Home Page',
+                  description: 'Home page description',
                 },
               },
             }),
           },
           ContentType: 'application/json',
-        });
-      const getTopPagesForSiteStub = [
-        { getUrl: () => 'http://example.com/blog/page1' },
-        {
-          getUrl: () => 'http://example.com/blog/page2',
-        },
-        {
-          getUrl: () => 'http://example.com/',
-        },
-      ];
-      dataAccessStub.SiteTopPage.allBySiteId.resolves(topPages);
-      dataAccessStub.SiteTopPage.allBySiteIdAndSourceAndGeo.resolves(getTopPagesForSiteStub);
-      const addAuditStub = sinon.stub().resolves({ getId: () => 'audit-id' });
-      dataAccessStub.Audit.create = addAuditStub;
-      const mockCalculateCPCValue = sinon.stub().resolves(2);
-      const auditStub = await esmock('../../src/metatags/handler.js', {
-        '../../src/support/utils.js': { getRUMDomainkey: mockGetRUMDomainkey, calculateCPCValue: mockCalculateCPCValue },
-        '@adobe/spacecat-shared-rum-api-client': RUMAPIClientStub,
+        };
+
+        // Setup S3 client responses
+        s3ClientStub.send = sinon.stub();
+        s3ClientStub.send
+          .withArgs(sinon.match.instanceOf(ListObjectsV2Command))
+          .resolves(listObjectsResponse);
+
+        s3ClientStub.send
+          .withArgs(sinon.match.instanceOf(GetObjectCommand).and(sinon.match.has('input', {
+            Bucket: 'test-bucket',
+            Key: 'scrapes/site-id/blog/page1/scrape.json',
+          })))
+          .resolves(page1Response);
+
+        s3ClientStub.send
+          .withArgs(sinon.match.instanceOf(GetObjectCommand).and(sinon.match.has('input', {
+            Bucket: 'test-bucket',
+            Key: 'scrapes/site-id/blog/page2/scrape.json',
+          })))
+          .resolves(page2Response);
+
+        s3ClientStub.send
+          .withArgs(sinon.match.instanceOf(GetObjectCommand).and(sinon.match.has('input', {
+            Bucket: 'test-bucket',
+            Key: 'scrapes/site-id/blog/page3/scrape.json',
+          })))
+          .resolves(page3Response);
+
+        s3ClientStub.send
+          .withArgs(sinon.match.instanceOf(GetObjectCommand).and(sinon.match.has('input', {
+            Bucket: 'test-bucket',
+            Key: 'scrapes/site-id/scrape.json',
+          })))
+          .resolves(homePageResponse);
+
+        context = {
+          site,
+          audit,
+          finalUrl: 'http://example.com',
+          log: logStub,
+          s3Client: s3ClientStub,
+          dataAccess: dataAccessStub,
+          env: {
+            S3_SCRAPER_BUCKET_NAME: 'test-bucket',
+          },
+        };
       });
-      const auditInstance = auditStub.default;
-      const response = await auditInstance.runner('http://example.com', context, site);
-      expect(response).to.deep.equal({
-        auditResult: {
-          detectedTags: {
+
+      afterEach(() => {
+        sinon.restore();
+      });
+
+      it('should successfully run audit and generate suggestions', async () => {
+        const mockGetRUMDomainkey = sinon.stub().resolves('mockedDomainKey');
+        const mockCalculateCPCValue = sinon.stub().resolves(5000);
+        const auditStub = await esmock('../../src/metatags/handler.js', {
+          '../../src/support/utils.js': { getRUMDomainkey: mockGetRUMDomainkey, calculateCPCValue: mockCalculateCPCValue },
+          '@adobe/spacecat-shared-rum-api-client': RUMAPIClientStub,
+          '../../src/metatags/metatags-auto-suggest.js': sinon.stub().resolves({
             '/blog/page1': {
-              h1: {
-                seoImpact: 'High',
-                issue: 'Missing H1',
-                issueDetails: 'H1 tag is missing',
-                seoRecommendation: 'Should be present',
-              },
               title: {
-                tagContent: 'Test Page',
-                seoImpact: 'High',
-                issue: 'Duplicate Title',
-                issueDetails: '2 pages share same title',
-                seoRecommendation: 'Unique across pages',
-              },
-              description: {
-                seoImpact: 'High',
-                issue: 'Empty Description',
-                issueDetails: 'Description tag is empty',
-                seoRecommendation: '140-160 characters long',
+                aiSuggestion: 'AI Suggested Title 1',
+                aiRationale: 'AI Rationale 1',
               },
             },
             '/blog/page2': {
-              description: {
-                seoImpact: 'High',
-                issue: 'Missing Description',
-                issueDetails: 'Description tag is missing',
-                seoRecommendation: 'Should be present',
-              },
               title: {
-                tagContent: 'Test Page',
-                seoImpact: 'High',
-                issue: 'Duplicate Title',
-                issueDetails: '2 pages share same title',
-                seoRecommendation: 'Unique across pages',
-              },
-              h1: {
-                tagContent: 'This is a dummy H1 that is intentionally made to be overly lengthy from SEO perspective',
-                seoImpact: 'Moderate',
-                issue: 'H1 too long',
-                issueDetails: '17 chars over limit',
-                seoRecommendation: 'Below 70 characters',
+                aiSuggestion: 'AI Suggested Title 2',
+                aiRationale: 'AI Rationale 2',
               },
             },
-          },
-          finalUrl: 'http://example.com',
-          fullAuditRef: '',
-          sourceS3Folder: 'test-bucket/scrapes/site-id/',
-        },
-        fullAuditRef: 'http://example.com',
-      });
-    }).timeout(5000);
-
-    it('should calculate projected traffic for detected tags', async () => {
-      const RUMAPIClientStub = {
-        createFrom: sinon.stub().returns({
-          query: sinon.stub().resolves([
-            {
-              url: 'http://example.com/blog/page1',
-              total: 100,
-              earned: 50000,
-              owned: 70,
-              paid: 10000,
-            },
-          ]),
-        }),
-      };
-      const mockGetRUMDomainkey = sinon.stub().resolves('mockedDomainKey');
-      const metatagsOppty = {
-        getId: () => 'opportunity-id',
-        setAuditId: sinon.stub(),
-        save: sinon.stub(),
-        getSuggestions: sinon.stub().returns([]),
-        addSuggestions: sinon.stub().returns({ errorItems: [], createdItems: [1, 2, 3] }),
-        getType: () => 'meta-tags',
-      };
-      const site = {
-        getIsLive: sinon.stub().returns(true),
-        getId: sinon.stub().returns('site-id'),
-        getBaseURL: sinon.stub().returns('http://example.com'),
-      };
-      const topPages = [{ getURL: 'http://example.com/blog/page1', getTopKeyword: sinon.stub().returns('page') },
-        { getURL: 'http://example.com/blog/page2', getTopKeyword: sinon.stub().returns('Test') }];
+          }),
+        });
+        const result = await auditStub.runAuditAndGenerateSuggestions(context);
 
-      dataAccessStub.Site.findById.resolves(site);
-      dataAccessStub.Configuration.findLatest.resolves({
-        isHandlerEnabledForSite: sinon.stub().returns(true),
+        expect(result).to.deep.equal({ status: 'complete' });
+        expect(s3ClientStub.send).to.have.been.called;
+        expect(metatagsOppty.save).to.have.been.called;
       });
-      dataAccessStub.SiteTopPage.allBySiteId.resolves(topPages);
-      dataAccessStub.Opportunity = {
-        allBySiteIdAndStatus: sinon.stub().returns([metatagsOppty]),
-      };
-      s3ClientStub.send
-        .withArgs(sinon.match.instanceOf(ListObjectsV2Command).and(sinon.match.has('input', {
-          Bucket: 'test-bucket',
-          Prefix: 'scrapes/site-id/',
-          MaxKeys: 1000,
-        })))
-        .resolves({
-          Contents: [
-            { Key: 'scrapes/site-id/blog/page1/scrape.json' },
-            { Key: 'scrapes/site-id/blog/page2/scrape.json' },
-          ],
-        });
 
-      s3ClientStub.send
-        .withArgs(sinon.match.instanceOf(GetObjectCommand).and(sinon.match.has('input', {
-          Bucket: 'test-bucket',
-          Key: 'scrapes/site-id/blog/page1/scrape.json',
-        }))).returns({
-          Body: {
-            transformToString: () => JSON.stringify({
-              scrapeResult: {
-                tags: {
-                  title: 'Test Page',
-                  description: '',
-                },
-              },
-            }),
-          },
-          ContentType: 'application/json',
+      it('should handle case when no tags are extracted', async () => {
+        const mockGetRUMDomainkey = sinon.stub().resolves('mockedDomainKey');
+        const mockCalculateCPCValue = sinon.stub().resolves(2);
+        const auditStub = await esmock('../../src/metatags/handler.js', {
+          '../../src/support/utils.js': { getRUMDomainkey: mockGetRUMDomainkey, calculateCPCValue: mockCalculateCPCValue },
+          '@adobe/spacecat-shared-rum-api-client': RUMAPIClientStub,
+          '../../src/metatags/metatags-auto-suggest.js': sinon.stub().resolves({}),
         });
-      s3ClientStub.send
-        .withArgs(sinon.match.instanceOf(GetObjectCommand).and(sinon.match.has('input', {
-          Bucket: 'test-bucket',
-          Key: 'scrapes/site-id/blog/page2/scrape.json',
-        }))).returns({
-          Body: {
-            transformToString: () => JSON.stringify({
-              scrapeResult: {
-                tags: {
-                  title: 'Test Page',
-                  h1: [
-                    'This is a dummy H1 that is intentionally made to be overly lengthy from SEO perspective',
-                  ],
+
+        // Override all S3 responses to have null tags
+        s3ClientStub.send
+          .withArgs(sinon.match.instanceOf(GetObjectCommand))
+          .returns({
+            Body: {
+              transformToString: () => JSON.stringify({
+                scrapeResult: {
+                  tags: null,
                 },
-              },
-            }),
-          },
-          ContentType: 'application/json',
-        });
-      const getTopPagesForSiteStub = [
-        { getUrl: () => 'http://example.com/blog/page1' },
-        {
-          getUrl: () => 'http://example.com/blog/page2',
-        },
-        {
-          getUrl: () => 'http://example.com/',
-        },
-      ];
-      dataAccessStub.SiteTopPage.allBySiteId.resolves(topPages);
-      dataAccessStub.SiteTopPage.allBySiteIdAndSourceAndGeo.resolves(getTopPagesForSiteStub);
-      const addAuditStub = sinon.stub().resolves({ getId: () => 'audit-id' });
-      dataAccessStub.Audit.create = addAuditStub;
-      const mockCalculateCPCValue = sinon.stub().resolves(2);
-      const auditStub = await esmock('../../src/metatags/handler.js', {
-        '../../src/support/utils.js': { getRUMDomainkey: mockGetRUMDomainkey, calculateCPCValue: mockCalculateCPCValue },
-        '@adobe/spacecat-shared-rum-api-client': RUMAPIClientStub,
-      });
-      const auditInstance = auditStub.default;
-      const response = await auditInstance.runner('http://example.com', context, site);
-      expect(response).to.deep.equal({
-        auditResult: {
-          projectedTrafficValue: 2400,
-          projectedTrafficLost: 1200,
-          detectedTags: {
-            '/blog/page1': {
-              h1: {
-                seoImpact: 'High',
-                issue: 'Missing H1',
-                issueDetails: 'H1 tag is missing',
-                seoRecommendation: 'Should be present',
-              },
-              title: {
-                tagContent: 'Test Page',
-                seoImpact: 'High',
-                issue: 'Duplicate Title',
-                issueDetails: '2 pages share same title',
-                seoRecommendation: 'Unique across pages',
-              },
-              description: {
-                seoImpact: 'High',
-                issue: 'Empty Description',
-                issueDetails: 'Description tag is empty',
-                seoRecommendation: '140-160 characters long',
-              },
-            },
-            '/blog/page2': {
-              description: {
-                seoImpact: 'High',
-                issue: 'Missing Description',
-                issueDetails: 'Description tag is missing',
-                seoRecommendation: 'Should be present',
-              },
-              title: {
-                tagContent: 'Test Page',
-                seoImpact: 'High',
-                issue: 'Duplicate Title',
-                issueDetails: '2 pages share same title',
-                seoRecommendation: 'Unique across pages',
-              },
-              h1: {
-                tagContent: 'This is a dummy H1 that is intentionally made to be overly lengthy from SEO perspective',
-                seoImpact: 'Moderate',
-                issue: 'H1 too long',
-                issueDetails: '17 chars over limit',
-                seoRecommendation: 'Below 70 characters',
-              },
+              }),
             },
-          },
-          finalUrl: 'http://example.com',
-          fullAuditRef: '',
-          sourceS3Folder: 'test-bucket/scrapes/site-id/',
-        },
-        fullAuditRef: 'http://example.com',
-      });
-    }).timeout(5000);
-  });
+            ContentType: 'application/json',
+          });
 
-  describe('opportunities handler method', () => {
-    let logStub;
-    let dataAccessStub;
-    let auditData;
-    let auditUrl;
-    let opportunity;
-    let context;
+        const result = await auditStub.runAuditAndGenerateSuggestions(context);
 
-    beforeEach(() => {
-      sinon.restore();
-      auditUrl = 'https://example.com';
-      opportunity = {
-        getId: () => 'opportunity-id',
-        setAuditId: sinon.stub(),
-        save: sinon.stub(),
-        getSuggestions: sinon.stub().returns(testData.existingSuggestions),
-        addSuggestions: sinon.stub().returns({ errorItems: [], createdItems: [1, 2, 3] }),
-        getType: () => 'meta-tags',
-        setData: () => {},
-        getData: () => {},
-      };
-      logStub = {
-        info: sinon.stub(),
-        debug: sinon.stub(),
-        error: sinon.stub(),
-      };
-      dataAccessStub = {
-        Opportunity: {
-          allBySiteIdAndStatus: sinon.stub().resolves([]),
-          create: sinon.stub(),
-        },
-        Suggestion: {
-          bulkUpdateStatus: sinon.stub(),
-        },
-      };
-      context = {
-        log: logStub,
-        dataAccess: dataAccessStub,
-        env: {
-          S3_SCRAPER_BUCKET_NAME: 'test-bucket',
-        },
-      };
-      auditData = testData.auditData;
-    });
+        expect(result).to.deep.equal({ status: 'complete' });
+        expect(logStub.error).to.have.been.calledWith('No Scraped tags found in S3 scrapes/site-id/blog/page3/scrape.json object');
+        expect(logStub.error).to.have.been.calledWith('Failed to extract tags from scraped content for bucket test-bucket and prefix scrapes/site-id/');
+      });
 
-    it('should create new opportunity and add suggestions', async () => {
-      opportunity.getType = () => 'meta-tags';
-      dataAccessStub.Opportunity.create = sinon.stub().returns(opportunity);
-      await opportunityAndSuggestions(auditUrl, auditData, context);
-      expect(dataAccessStub.Opportunity.create).to.be.calledWith(testData.OpportunityData);
-      expect(logStub.info).to.be.calledWith('Successfully synced Opportunity And Suggestions for site: site-id and meta-tags audit type.');
-    });
+      it('should handle RUM API errors gracefully', async () => {
+        const mockGetRUMDomainkey = sinon.stub().resolves('mockedDomainKey');
+        const mockCalculateCPCValue = sinon.stub().resolves(2);
+        const auditStub = await esmock('../../src/metatags/handler.js', {
+          '../../src/support/utils.js': { getRUMDomainkey: mockGetRUMDomainkey, calculateCPCValue: mockCalculateCPCValue },
+          '@adobe/spacecat-shared-rum-api-client': RUMAPIClientStub,
+          '../../src/metatags/metatags-auto-suggest.js': sinon.stub().resolves({}),
+        });
+        // Override RUM API response to simulate error
+        RUMAPIClientStub.createFrom().query.rejects(new Error('RUM API Error'));
 
-    it('should use existing opportunity and add suggestions', async () => {
-      dataAccessStub.Opportunity.allBySiteIdAndStatus.resolves([opportunity]);
-      await opportunityAndSuggestions(auditUrl, auditData, context);
-      expect(opportunity.save).to.be.calledOnce;
-      expect(logStub.info).to.be.calledWith('Successfully synced Opportunity And Suggestions for site: site-id and meta-tags audit type.');
-    });
+        const result = await auditStub.runAuditAndGenerateSuggestions(context);
 
-    it('should throw error if fetching opportunity fails', async () => {
-      dataAccessStub.Opportunity.allBySiteIdAndStatus.rejects(new Error('some-error'));
-      try {
-        await opportunityAndSuggestions(auditUrl, auditData, context);
-      } catch (err) {
-        expect(err.message).to.equal('Failed to fetch opportunities for siteId site-id: some-error');
-      }
-      expect(logStub.error).to.be.calledWith('Fetching opportunities for siteId site-id failed with error: some-error');
+        expect(result).to.deep.equal({ status: 'complete' });
+        expect(logStub.warn).to.have.been.calledWith('Error while calculating projected traffic for http://example.com : site-id', sinon.match.instanceOf(Error));
+      });
     });
 
-    it('should throw error if creating opportunity fails', async () => {
-      dataAccessStub.Opportunity.allBySiteIdAndStatus.returns([]);
-      dataAccessStub.Opportunity.create = sinon.stub().rejects(new Error('some-error'));
-      try {
-        await opportunityAndSuggestions(auditUrl, auditData, context);
-      } catch (err) {
-        expect(err.message).to.equal('some-error');
-      }
-      expect(logStub.error).to.be.calledWith('Failed to create new opportunity for siteId site-id and auditId audit-id: some-error');
-    });
+    describe('removeTrailingSlash', () => {
+      it('should remove trailing slash from URL', () => {
+        const url = 'http://example.com/';
+        const result = removeTrailingSlash(url);
+        expect(result).to.equal('http://example.com');
+      });
 
-    it('should sync existing suggestions with new suggestions', async () => {
-      dataAccessStub.Opportunity.allBySiteIdAndStatus.resolves([opportunity]);
-      opportunity.getSuggestions.returns(testData.existingSuggestions);
-      await opportunityAndSuggestions(auditUrl, auditData, context);
-      expect(opportunity.save).to.be.calledOnce;
-      expect(logStub.info).to.be.calledWith('Successfully synced Opportunity And Suggestions for site: site-id and meta-tags audit type.');
+      it('should not modify URL without trailing slash', () => {
+        const url = 'http://example.com';
+        const result = removeTrailingSlash(url);
+        expect(result).to.equal(url);
+      });
+
+      it('should handle empty string', () => {
+        const url = '';
+        const result = removeTrailingSlash(url);
+        expect(result).to.equal('');
+      });
     });
 
-    it('should mark existing suggestions OUTDATED if not present in audit data', async () => {
-      dataAccessStub.Opportunity.allBySiteIdAndStatus.resolves([opportunity]);
-      opportunity.getSuggestions.returns(testData.existingSuggestions);
-      const auditDataModified = {
-        type: 'meta-tags',
-        siteId: 'site-id',
-        id: 'audit-id',
-        auditResult: {
-          finalUrl: 'www.test-site.com/',
-          detectedTags: {
-            '/page1': {
-              title: {
-                tagContent: 'Lovesac - 404 Not Found',
-                duplicates: [
-                  '/page4',
-                  '/page5',
-                ],
-                seoRecommendation: 'Unique across pages',
-                issue: 'Duplicate Title',
-                issueDetails: '3 pages share same title',
-                seoImpact: 'High',
-              },
+    describe('metatagsAutoSuggest', () => {
+      let metatagsAutoSuggest;
+      let s3Client;
+      let dataAccess;
+      let log;
+      let Configuration;
+      let getPresignedUrlStub;
+      let genvarClientStub;
+      let siteStub;
+      let allTags;
+
+      beforeEach(async () => {
+        s3Client = {};
+        log = {
+          info: sinon.stub(),
+          error: sinon.stub(),
+          warn: sinon.stub(),
+          debug: sinon.stub(),
+        };
+        Configuration = {
+          findLatest: sinon.stub().resolves({
+            isHandlerEnabledForSite: sinon.stub().returns(true),
+          }),
+        };
+        dataAccess = { Configuration };
+        genvarClientStub = {
+          generateSuggestions: sinon.stub().resolves({
+            '/about-us': {
               h1: {
-                seoRecommendation: 'Should be present',
-                issue: 'Missing H1',
-                issueDetails: 'H1 tag is missing',
-                seoImpact: 'High',
+                aiRationale: 'The H1 tag is catchy and broad...',
+                aiSuggestion: 'Our Story: Innovating Comfort for Every Home',
               },
             },
-            '/page2': {
-              title: {
-                seoRecommendation: '40-60 characters long',
-                issue: 'Empty Title',
-                issueDetails: 'Title tag is empty',
-                seoImpact: 'High',
+            '/add-on-and-refresh': {
+              description: {
+                aiRationale: 'The description emphasizes the brand\'s core values...',
+                aiSuggestion: 'Elevate your home with Lovesac\'s customizable add-ons...',
               },
               h1: {
-                tagContent: '["We Can All Win Together","We Say As We Do"]',
-                seoRecommendation: '1 H1 on a page',
-                issue: 'Multiple H1 on page',
-                issueDetails: '2 H1 detected',
-                seoImpact: 'Moderate',
+                aiRationale: 'The H1 tag is catchy and directly addresses the user\'s intent...',
+                aiSuggestion: 'Revitalize Your Home with Lovesac Add-Ons',
               },
             },
+          }),
+        };
+        context = {
+          s3Client,
+          dataAccess,
+          log,
+          env: {
+            GENVARHOST: 'https://genvar.endpoint',
+            GENVAR_IMS_ORG_ID: 'test-org-id',
           },
-        },
-      };
-      await opportunityAndSuggestions(auditUrl, auditDataModified, context);
-      expect(dataAccessStub.Suggestion.bulkUpdateStatus).to.be.calledWith(testData.existingSuggestions.splice(0, 2), 'OUTDATED');
-      expect(opportunity.save).to.be.calledOnce;
-      expect(logStub.info).to.be.calledWith('Successfully synced Opportunity And Suggestions for site: site-id and meta-tags audit type.');
-    });
-
-    it('should preserve existing AI suggestions and overrides when syncing', async () => {
-      dataAccessStub.Opportunity.allBySiteIdAndStatus.resolves([opportunity]);
-
-      // Setup existing suggestion with AI data and overrides
-      const existingSuggestion = {
-        getData: () => ({
-          url: 'https://example.com/page1',
-          tagName: 'title',
-          tagContent: 'Original Title',
-          issue: 'Title too short',
-          seoImpact: 'High',
-          aiSuggestion: 'AI Generated Title',
-          aiRationale: 'AI explanation for the title',
-          toOverride: true,
-        }),
-        getStatus: () => 'pending',
-        remove: sinon.stub(),
-        setData: sinon.stub(),
-        save: sinon.stub(),
-      };
-
-      opportunity.getSuggestions.returns([existingSuggestion]);
-
-      // Create audit data with different content for same URL
-      const modifiedAuditData = {
-        siteId: 'site-id',
-        auditId: 'audit-id',
-        auditResult: {
-          finalUrl: 'https://example.com',
+        };
+        allTags = {
           detectedTags: {
-            '/page1': {
-              title: {
-                tagContent: 'Original Title',
-                issue: 'Title too short',
-                seoImpact: 'High',
-              },
-            },
+            '/about-us': { h1: {} },
+            '/add-on-and-refresh': { description: {}, h1: {} },
           },
-        },
-      };
-
-      await opportunityAndSuggestions(auditUrl, modifiedAuditData, context);
-
-      // Verify that existing suggestion was updated properly
-      expect(opportunity.save).to.be.calledOnce;
-      expect(existingSuggestion.setData).to.be.calledOnce;
-
-      const setDataCall = existingSuggestion.setData.getCall(0);
-      const updatedData = setDataCall.args[0];
+          extractedTags: {
+            '/about-us': { s3key: 'about-us-key' },
+            '/add-on-and-refresh': { s3key: 'add-on-key' },
+          },
+          healthyTags: {},
+        };
 
-      // Verify the original AI data and override flags were preserved
-      expect(updatedData).to.deep.include({
-        aiSuggestion: 'AI Generated Title',
-        aiRationale: 'AI explanation for the title',
-        toOverride: true,
+        metatagsAutoSuggest = await esmock('../../src/metatags/metatags-auto-suggest.js', {
+          '@adobe/spacecat-shared-gpt-client': {
+            GenvarClient: {
+              createFrom: () => genvarClientStub,
+            },
+          },
+          '@aws-sdk/s3-request-presigner': { getSignedUrl: getPresignedUrlStub },
+        });
+        siteStub = {
+          getBaseURL: sinon.stub().returns('https://example.com'),
+        };
       });
 
-      // Verify the suggestion was saved
-      expect(existingSuggestion.save).to.be.calledOnce;
-    });
-
-    it('should throw error if suggestions fail to create', async () => {
-      sinon.stub(GoogleClient, 'createFrom').resolves({});
-      dataAccessStub.Opportunity.allBySiteIdAndStatus.resolves([opportunity]);
-      opportunity.getSiteId = () => 'site-id';
-      opportunity.addSuggestions = sinon.stub().returns({ errorItems: [{ item: 1, error: 'some-error' }], createdItems: [] });
-      try {
-        await opportunityAndSuggestions(auditUrl, auditData, context);
-      } catch (err) {
-        expect(err.message).to.equal('Failed to create suggestions for siteId site-id');
-      }
-      expect(opportunity.save).to.be.calledOnce;
-      expect(logStub.error).to.be.calledWith('Suggestions for siteId site-id contains 1 items with errors');
-      expect(logStub.error).to.be.calledTwice;
-    });
+      afterEach(() => {
+        sinon.restore();
+      });
 
-    it('should take rank as -1 if issue is not known', async () => {
-      dataAccessStub.Opportunity.allBySiteIdAndStatus.resolves([opportunity]);
-      const auditDataModified = {
-        ...testData.auditData,
-      };
-      auditDataModified.auditResult.detectedTags['/page1'].title.issue = 'some random issue';
-      const expectedSuggestionModified = [
-        ...testData.expectedSuggestions,
-      ];
-      expectedSuggestionModified[0].data.issue = 'some random issue';
-      expectedSuggestionModified[0].data.rank = -1;
-      expectedSuggestionModified[0].rank = -1;
-      await opportunityAndSuggestions(auditUrl, auditData, context);
-      expect(opportunity.save).to.be.calledOnce;
-      expect(logStub.info).to.be.calledWith('Successfully synced Opportunity And Suggestions for site: site-id and meta-tags audit type.');
-    });
-  });
+      it('should handle disabled handler for site', async () => {
+        Configuration.findLatest.resolves({
+          isHandlerEnabledForSite: sinon.stub().returns(false),
+        });
+        await metatagsAutoSuggest(allTags, context, siteStub);
+        expect(log.info.calledWith('Metatags auto-suggest is disabled for site')).to.be.true;
+      });
 
-  describe('removeTrailingSlash', () => {
-    it('should remove trailing slash from URL', () => {
-      const url = 'http://example.com/';
-      const result = removeTrailingSlash(url);
-      expect(result).to.equal('http://example.com');
-    });
+      it('should handle missing Genvar endpoint', async () => {
+        context.env.GENVAR_HOST = '';
+        try {
+          await metatagsAutoSuggest(allTags, context, siteStub);
+        } catch (error) {
+          expect(error.message).to.equal('Metatags Auto-suggest failed: Missing Genvar endpoint or genvar ims orgId');
+        }
+      });
 
-    it('should not modify URL without trailing slash', () => {
-      const url = 'http://example.com';
-      const result = removeTrailingSlash(url);
-      expect(result).to.equal(url);
-    });
+      it('should handle missing genvar ims orgId', async () => {
+        context.env.GENVAR_IMS_ORG_ID = '';
 
-    it('should handle empty string', () => {
-      const url = '';
-      const result = removeTrailingSlash(url);
-      expect(result).to.equal('');
-    });
-  });
+        try {
+          await metatagsAutoSuggest(allTags, context, siteStub);
+        } catch (error) {
+          expect(error.message).to.equal('Metatags Auto-suggest failed: Missing Genvar endpoint or genvar ims orgId1');
+        }
+      });
 
-  describe('metatagsAutoSuggest', () => {
-    let metatagsAutoSuggest;
-    let context;
-    let s3Client;
-    let dataAccess;
-    let log;
-    let Configuration;
-    let getPresignedUrlStub;
-    let genvarClientStub;
-    let siteStub;
-    let allTags;
-
-    beforeEach(async () => {
-      s3Client = {};
-      log = {
-        info: sinon.stub(),
-        error: sinon.stub(),
-        warn: sinon.stub(),
-        debug: sinon.stub(),
-      };
-      Configuration = {
-        findLatest: sinon.stub().resolves({
-          isHandlerEnabledForSite: sinon.stub().returns(true),
-        }),
-      };
-      dataAccess = { Configuration };
-      genvarClientStub = {
-        generateSuggestions: sinon.stub().resolves({
+      it('should generate presigned URLs and call Genvar API', async () => {
+        genvarClientStub.generateSuggestions.resolves({
           '/about-us': {
             h1: {
               aiRationale: 'The H1 tag is catchy and broad...',
@@ -1516,118 +1063,38 @@ describe('Meta Tags', () => {
               aiSuggestion: 'Revitalize Your Home with Lovesac Add-Ons',
             },
           },
-        }),
-      };
-      context = {
-        s3Client,
-        dataAccess,
-        log,
-        env: {
-          GENVARHOST: 'https://genvar.endpoint',
-          GENVAR_IMS_ORG_ID: 'test-org-id',
-        },
-      };
-      allTags = {
-        detectedTags: {
-          '/about-us': { h1: {} },
-          '/add-on-and-refresh': { description: {}, h1: {} },
-        },
-        extractedTags: {
-          '/about-us': { s3key: 'about-us-key' },
-          '/add-on-and-refresh': { s3key: 'add-on-key' },
-        },
-        healthyTags: {},
-      };
+        });
 
-      metatagsAutoSuggest = await esmock('../../src/metatags/metatags-auto-suggest.js', {
-        '@adobe/spacecat-shared-gpt-client': { GenvarClient: { createFrom: () => genvarClientStub } },
-        '@aws-sdk/s3-request-presigner': { getSignedUrl: getPresignedUrlStub },
+        const response = await metatagsAutoSuggest(allTags, context, siteStub);
+
+        expect(log.debug.calledWith('Generated presigned URLs')).to.be.true;
+        expect(log.info.calledWith('Generated AI suggestions for Meta-tags using Genvar.')).to.be.true;
+        expect(response['/about-us'].h1.aiSuggestion).to.equal('Our Story: Innovating Comfort for Every Home');
+        expect(response['/add-on-and-refresh'].description.aiSuggestion).to.equal('Elevate your home with Lovesac\'s customizable add-ons...');
+        expect(response['/add-on-and-refresh'].h1.aiSuggestion).to.equal('Revitalize Your Home with Lovesac Add-Ons');
+      }).timeout(15000);
+
+      it('should log an error and throw if the Genvar API call fails', async () => {
+        genvarClientStub.generateSuggestions.throws(new Error('Genvar API failed'));
+        let err;
+        try {
+          await metatagsAutoSuggest(allTags, context, siteStub);
+        } catch (error) {
+          err = error;
+        }
+        expect(err.message).to.equal('Genvar API failed');
       });
-      siteStub = {
-        getBaseURL: sinon.stub().returns('https://example.com'),
-      };
-    });
 
-    afterEach(() => {
-      sinon.restore();
-    });
-
-    it('should handle disabled handler for site', async () => {
-      Configuration.findLatest.resolves({
-        isHandlerEnabledForSite: sinon.stub().returns(false),
+      it('should log an error and throw if the Genvar API response is invalid', async () => {
+        genvarClientStub.generateSuggestions.resolves(5);
+        let err;
+        try {
+          await metatagsAutoSuggest(allTags, context, siteStub);
+        } catch (error) {
+          err = error;
+        }
+        expect(err.message).to.equal('Invalid response received from Genvar API: 5');
       });
-      await metatagsAutoSuggest(allTags, context, siteStub);
-      expect(log.info.calledWith('Metatags auto-suggest is disabled for site')).to.be.true;
-    });
-
-    it('should handle missing Genvar endpoint', async () => {
-      context.env.GENVAR_HOST = '';
-      try {
-        await metatagsAutoSuggest(allTags, context, siteStub);
-      } catch (error) {
-        expect(error.message).to.equal('Metatags Auto-suggest failed: Missing Genvar endpoint or genvar ims orgId');
-      }
-    });
-
-    it('should handle missing genvar ims orgId', async () => {
-      context.env.GENVAR_IMS_ORG_ID = '';
-
-      try {
-        await metatagsAutoSuggest(allTags, context, siteStub);
-      } catch (error) {
-        expect(error.message).to.equal('Metatags Auto-suggest failed: Missing Genvar endpoint or genvar ims orgId1');
-      }
-    });
-
-    it('should generate presigned URLs and call Genvar API', async () => {
-      genvarClientStub.generateSuggestions.resolves({
-        '/about-us': {
-          h1: {
-            aiRationale: 'The H1 tag is catchy and broad...',
-            aiSuggestion: 'Our Story: Innovating Comfort for Every Home',
-          },
-        },
-        '/add-on-and-refresh': {
-          description: {
-            aiRationale: 'The description emphasizes the brand\'s core values...',
-            aiSuggestion: 'Elevate your home with Lovesac\'s customizable add-ons...',
-          },
-          h1: {
-            aiRationale: 'The H1 tag is catchy and directly addresses the user\'s intent...',
-            aiSuggestion: 'Revitalize Your Home with Lovesac Add-Ons',
-          },
-        },
-      });
-
-      const response = await metatagsAutoSuggest(allTags, context, siteStub);
-
-      expect(log.debug.calledWith('Generated presigned URLs')).to.be.true;
-      expect(log.info.calledWith('Generated AI suggestions for Meta-tags using Genvar.')).to.be.true;
-      expect(response['/about-us'].h1.aiSuggestion).to.equal('Our Story: Innovating Comfort for Every Home');
-      expect(response['/add-on-and-refresh'].description.aiSuggestion).to.equal('Elevate your home with Lovesac\'s customizable add-ons...');
-      expect(response['/add-on-and-refresh'].h1.aiSuggestion).to.equal('Revitalize Your Home with Lovesac Add-Ons');
-    }).timeout(15000);
-
-    it('should log an error and throw if the Genvar API call fails', async () => {
-      genvarClientStub.generateSuggestions.throws(new Error('Genvar API failed'));
-      let err;
-      try {
-        await metatagsAutoSuggest(allTags, context, siteStub);
-      } catch (error) {
-        err = error;
-      }
-      expect(err.message).to.equal('Genvar API failed');
-    });
-
-    it('should log an error and throw if the Genvar API response is invalid', async () => {
-      genvarClientStub.generateSuggestions.resolves(5);
-      let err;
-      try {
-        await metatagsAutoSuggest(allTags, context, siteStub);
-      } catch (error) {
-        err = error;
-      }
-      expect(err.message).to.equal('Invalid response received from Genvar API: 5');
     });
   });
 });
diff --git a/test/fixtures/meta-tags-data.js b/test/fixtures/meta-tags-data.js
index 1f2e34834f..c9bb7c107c 100644
--- a/test/fixtures/meta-tags-data.js
+++ b/test/fixtures/meta-tags-data.js
@@ -15,7 +15,7 @@ const testData = {
   auditData: {
     type: 'meta-tags',
     siteId: 'site-id',
-    id: 'audit-id',
+    auditId: 'audit-id',
     auditResult: {
       finalUrl: 'www.test-site.com/',
       detectedTags: {


Reviews:
user: login: solaris007
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-09T11:49:48Z
user: login: herzog31
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-09T12:57:04Z
user: login: Danialkafeel
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T07:58:52Z
user: login: Danialkafeel
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T07:59:09Z
user: login: Danialkafeel
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T07:59:17Z
user: login: Danialkafeel
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T07:59:23Z
user: login: Danialkafeel
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T07:59:29Z
user: login: herzog31
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-12T08:16:36Z
user: login: solaris007
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-12T15:31:41Z

Review comments:
diff_hunk: @@ -1,3 +1,4 @@
 .vscode/*
 coverage/*
-scripts/*
\ No newline at end of file
+scripts/*
+test/audits/metatags.test.js
path: .eslintignore
user: login: solaris007
body: why should this be ignored?
created_at: 2025-05-09T11:48:08Z
author_association: MEMBER
diff_hunk: @@ -12,6 +12,7 @@
     "src/**/*.js"
   ],
   "exclude": [
-    "src/index-local.js"
+    "src/index-local.js",
+    "src/metatags/*"
path: .nycrc.json
user: login: solaris007
body: why should this be ignored?
created_at: 2025-05-09T11:48:18Z
author_association: MEMBER
diff_hunk: @@ -191,9 +192,12 @@ async function calculateProjectedTraffic(context, auditUrl, siteId, detectedTags
   }
 }
 
-export async function auditMetaTagsRunner(auditUrl, context, site) {
-  const { log, s3Client, dataAccess } = context;
+export async function runAuditAndGenerateSuggestions(context) {
+  const {
+    site, audit, finalUrl, log, s3Client, dataAccess,
+  } = context;
   // Get top pages for a site
+  log.info(`Running generating suggestions step for meta-tags audit on ${finalUrl}`);
path: src/metatags/handler.js
user: login: solaris007
body: is this log really required? what purpose does it serve for production issue analysis? let's be mindful of log volume on coralogix
created_at: 2025-05-09T11:49:20Z
author_association: MEMBER
diff_hunk: @@ -252,18 +256,60 @@ export async function auditMetaTagsRunner(auditUrl, context, site) {
     detectedTags: updatedDetectedTags,
     sourceS3Folder: `${bucketName}/${prefix}`,
     fullAuditRef: '',
-    finalUrl: auditUrl,
+    finalUrl,
     ...(projectedTrafficLost && { projectedTrafficLost }),
     ...(projectedTrafficValue && { projectedTrafficValue }),
   };
-  return {
+  await opportunityAndSuggestions(finalUrl, {
+    siteId: site.getId(),
+    auditId: audit.getId(),
     auditResult,
-    fullAuditRef: auditUrl,
+  }, context);
+
+  return {
+    status: 'complete',
+  };
+}
+
+export async function importTopPages(context) {
+  const { site, finalUrl, log } = context;
+
+  log.info(`Importing top pages for ${finalUrl}, context: ${JSON.stringify(context)}`);
path: src/metatags/handler.js
user: login: solaris007
body: is this log really required? what purpose does it serve for production issue analysis? let's be mindful of log volume on coralogix
created_at: 2025-05-09T11:49:34Z
author_association: MEMBER
diff_hunk: @@ -252,18 +256,60 @@ export async function auditMetaTagsRunner(auditUrl, context, site) {
     detectedTags: updatedDetectedTags,
     sourceS3Folder: `${bucketName}/${prefix}`,
     fullAuditRef: '',
-    finalUrl: auditUrl,
+    finalUrl,
     ...(projectedTrafficLost && { projectedTrafficLost }),
     ...(projectedTrafficValue && { projectedTrafficValue }),
   };
-  return {
+  await opportunityAndSuggestions(finalUrl, {
+    siteId: site.getId(),
+    auditId: audit.getId(),
     auditResult,
-    fullAuditRef: auditUrl,
+  }, context);
+
+  return {
+    status: 'complete',
+  };
+}
+
+export async function importTopPages(context) {
+  const { site, finalUrl, log } = context;
+
+  log.info(`Importing top pages for ${finalUrl}, context: ${JSON.stringify(context)}`);
+
+  const s3BucketPath = `scrapes/${site.getId()}/`;
+  return {
+    type: 'top-pages',
+    siteId: site.getId(),
+    auditResult: { status: 'preparing', finalUrl },
+    fullAuditRef: s3BucketPath,
+  };
+}
+
+export async function submitForScraping(context) {
+  const {
+    site,
+    dataAccess,
+    log,
+    finalUrl,
+  } = context;
+  const { SiteTopPage } = dataAccess;
+  const topPages = await SiteTopPage.allBySiteIdAndSourceAndGeo(site.getId(), 'ahrefs', 'global');
+  if (topPages.length === 0) {
+    throw new Error('No top pages found for site');
+  }
+
+  log.info(`Submitting for scraping ${topPages.length} top pages for site ${site.getId()}, finalUrl: ${finalUrl}, context: ${JSON.stringify(context)}`);
+
+  return {
+    urls: topPages.map((topPage) => ({ url: topPage.getUrl() })),
+    siteId: site.getId(),
path: src/metatags/handler.js
user: login: herzog31
body: According to the [documentation](https://github.com/adobe/spacecat-audit-worker?tab=readme-ov-file#destinations), this should be `jobId` instead of `siteId`, right?
created_at: 2025-05-09T12:57:01Z
author_association: MEMBER
diff_hunk: @@ -252,18 +256,60 @@ export async function auditMetaTagsRunner(auditUrl, context, site) {
     detectedTags: updatedDetectedTags,
     sourceS3Folder: `${bucketName}/${prefix}`,
     fullAuditRef: '',
-    finalUrl: auditUrl,
+    finalUrl,
     ...(projectedTrafficLost && { projectedTrafficLost }),
     ...(projectedTrafficValue && { projectedTrafficValue }),
   };
-  return {
+  await opportunityAndSuggestions(finalUrl, {
+    siteId: site.getId(),
+    auditId: audit.getId(),
     auditResult,
-    fullAuditRef: auditUrl,
+  }, context);
+
+  return {
+    status: 'complete',
+  };
+}
+
+export async function importTopPages(context) {
+  const { site, finalUrl, log } = context;
+
+  log.info(`Importing top pages for ${finalUrl}, context: ${JSON.stringify(context)}`);
+
+  const s3BucketPath = `scrapes/${site.getId()}/`;
+  return {
+    type: 'top-pages',
+    siteId: site.getId(),
+    auditResult: { status: 'preparing', finalUrl },
+    fullAuditRef: s3BucketPath,
+  };
+}
+
+export async function submitForScraping(context) {
+  const {
+    site,
+    dataAccess,
+    log,
+    finalUrl,
+  } = context;
+  const { SiteTopPage } = dataAccess;
+  const topPages = await SiteTopPage.allBySiteIdAndSourceAndGeo(site.getId(), 'ahrefs', 'global');
+  if (topPages.length === 0) {
+    throw new Error('No top pages found for site');
+  }
+
+  log.info(`Submitting for scraping ${topPages.length} top pages for site ${site.getId()}, finalUrl: ${finalUrl}, context: ${JSON.stringify(context)}`);
+
+  return {
+    urls: topPages.map((topPage) => ({ url: topPage.getUrl() })),
+    siteId: site.getId(),
path: src/metatags/handler.js
user: login: Danialkafeel
body: Hi @herzog31 
Yes it requires jobId, but the data-access layer transforms the siteId to jobId. You can refer it here: https://github.com/adobe/spacecat-shared/blob/bbecdb9bb40bca785ad71fbbadfa4e7263bf35e4/packages/spacecat-shared-data-access/src/models/audit/audit.model.js#L131

![Screenshot 2025-05-12 at 1 27 35 PM](https://github.com/user-attachments/assets/1970ff19-bd7b-4c65-b4b9-02740f7c616f)

created_at: 2025-05-12T07:58:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,3 +1,4 @@
 .vscode/*
 coverage/*
-scripts/*
\ No newline at end of file
+scripts/*
+test/audits/metatags.test.js
path: .eslintignore
user: login: Danialkafeel
body: It was a leftover. Had skipped this for testing, updated now
created_at: 2025-05-12T07:59:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -12,6 +12,7 @@
     "src/**/*.js"
   ],
   "exclude": [
-    "src/index-local.js"
+    "src/index-local.js",
+    "src/metatags/*"
path: .nycrc.json
user: login: Danialkafeel
body: It was a leftover. Had skipped this for testing, updated now
created_at: 2025-05-12T07:59:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -191,9 +192,12 @@ async function calculateProjectedTraffic(context, auditUrl, siteId, detectedTags
   }
 }
 
-export async function auditMetaTagsRunner(auditUrl, context, site) {
-  const { log, s3Client, dataAccess } = context;
+export async function runAuditAndGenerateSuggestions(context) {
+  const {
+    site, audit, finalUrl, log, s3Client, dataAccess,
+  } = context;
   // Get top pages for a site
+  log.info(`Running generating suggestions step for meta-tags audit on ${finalUrl}`);
path: src/metatags/handler.js
user: login: Danialkafeel
body: Removed
created_at: 2025-05-12T07:59:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -252,18 +256,60 @@ export async function auditMetaTagsRunner(auditUrl, context, site) {
     detectedTags: updatedDetectedTags,
     sourceS3Folder: `${bucketName}/${prefix}`,
     fullAuditRef: '',
-    finalUrl: auditUrl,
+    finalUrl,
     ...(projectedTrafficLost && { projectedTrafficLost }),
     ...(projectedTrafficValue && { projectedTrafficValue }),
   };
-  return {
+  await opportunityAndSuggestions(finalUrl, {
+    siteId: site.getId(),
+    auditId: audit.getId(),
     auditResult,
-    fullAuditRef: auditUrl,
+  }, context);
+
+  return {
+    status: 'complete',
+  };
+}
+
+export async function importTopPages(context) {
+  const { site, finalUrl, log } = context;
+
+  log.info(`Importing top pages for ${finalUrl}, context: ${JSON.stringify(context)}`);
path: src/metatags/handler.js
user: login: Danialkafeel
body: Removed
created_at: 2025-05-12T07:59:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -252,18 +256,60 @@ export async function auditMetaTagsRunner(auditUrl, context, site) {
     detectedTags: updatedDetectedTags,
     sourceS3Folder: `${bucketName}/${prefix}`,
     fullAuditRef: '',
-    finalUrl: auditUrl,
+    finalUrl,
     ...(projectedTrafficLost && { projectedTrafficLost }),
     ...(projectedTrafficValue && { projectedTrafficValue }),
   };
-  return {
+  await opportunityAndSuggestions(finalUrl, {
+    siteId: site.getId(),
+    auditId: audit.getId(),
     auditResult,
-    fullAuditRef: auditUrl,
+  }, context);
+
+  return {
+    status: 'complete',
+  };
+}
+
+export async function importTopPages(context) {
+  const { site, finalUrl, log } = context;
+
+  log.info(`Importing top pages for ${finalUrl}, context: ${JSON.stringify(context)}`);
+
+  const s3BucketPath = `scrapes/${site.getId()}/`;
+  return {
+    type: 'top-pages',
+    siteId: site.getId(),
+    auditResult: { status: 'preparing', finalUrl },
+    fullAuditRef: s3BucketPath,
+  };
+}
+
+export async function submitForScraping(context) {
+  const {
+    site,
+    dataAccess,
+    log,
+    finalUrl,
+  } = context;
+  const { SiteTopPage } = dataAccess;
+  const topPages = await SiteTopPage.allBySiteIdAndSourceAndGeo(site.getId(), 'ahrefs', 'global');
+  if (topPages.length === 0) {
+    throw new Error('No top pages found for site');
+  }
+
+  log.info(`Submitting for scraping ${topPages.length} top pages for site ${site.getId()}, finalUrl: ${finalUrl}, context: ${JSON.stringify(context)}`);
+
+  return {
+    urls: topPages.map((topPage) => ({ url: topPage.getUrl() })),
+    siteId: site.getId(),
path: src/metatags/handler.js
user: login: herzog31
body: Thanks for clarifying @Danialkafeel. I think the readme is a bit misleading in this regard. I will update my stepped audit PR as well to change it to `siteId` 👍 
created_at: 2025-05-12T08:16:35Z
author_association: MEMBER

PR comments:
user: login: github-actions[bot]
created_at: 2025-05-07T07:28:08Z
body: <!-- generated by: semantic-release-comment-action -->
This PR will trigger **a minor release** when merged.
author_association: NONE
user: login: solaris007
created_at: 2025-05-13T05:29:23Z
body: :tada: This PR is included in version 1.84.0 :tada:

The release is available on [GitHub release](https://github.com/adobe/spacecat-audit-worker/releases/tag/v1.84.0)

Your **[semantic-release](https://github.com/semantic-release/semantic-release)** bot :package::rocket:
author_association: MEMBER