OpenGEO
← Back to the journal

Tutorial · 4 min read

From a local Mock to a completed Job

Published Oct 13, 2026 · Updated Oct 13, 2026 · OpenGEO

Use the local Mock to understand the OpenGEO Job flow. Create a task, query progress with the same ID, then read observations belonging to that task. Simulated answers and Demo Brand are fictional, not real monitoring results.

Prepare the environment and start Mock

You need Node.js 22 or later, Git and pnpm 11. Run these commands in one terminal and keep it open. Mock listens only on 127.0.0.1. Dependency installation needs network access; the monitoring exercise calls only your machine.

bash
git clone https://github.com/jiangqiusuo/OpenGEO.git
cd OpenGEO
pnpm install
pnpm mock

Run the example in another terminal

Enter the same OpenGEO directory and run the included script. This Node command works in Windows PowerShell, macOS and Linux. No credentials need to be pasted into it.

bash
node examples/mock-to-job.mjs

The default address is http://127.0.0.1:8787. If the port is occupied, set OPEN_GEO_PORT in the Mock terminal and pass the new local address as the only script argument, for example node examples/mock-to-job.mjs http://127.0.0.1:8788. The script rejects external destinations and redirects to keep tutorial requests away from live services.

What happens during the request

POST /v1/monitor-runs returns 202 and queued. The returned id is used for later queries and result association; Location points to that Job. Use a new idempotency key for creation. The same key and body return the same Job, while changing the question causes a conflict.

The first local query returns running and the second succeeded with complete results. items then returns the corresponding observations. This is a predictable teaching flow. Follow API state for real waiting times, partial results and failures instead of assuming two queries always complete a task.

json
{
  "source": "local-mock",
  "job_id": "job_mock_1",
  "status_trace": ["queued", "running", "succeeded"],
  "result_state": "complete",
  "observation_count": 1
}

The output excerpt above omits answer. The task number depends on how many Jobs Mock has created. Check source is local-mock, the final state is succeeded, and every observation has the same job_id as the task.

Complete code you can copy

This code comes directly from examples/mock-to-job.mjs and is the same file available to download. Polling is limited to eight attempts, with a timeout on each request. Failure, cancellation, partial finalization or exceeding the window raises an error instead of waiting forever.

javascript
/* global console, process, fetch, AbortSignal */
import { randomUUID } from 'node:crypto';
import { resolve } from 'node:path';
import { pathToFileURL, URL } from 'node:url';
import { setTimeout as delay } from 'node:timers/promises';

export async function runTutorial(base = 'http://127.0.0.1:8787') {
  const origin = new URL(base);
  if (origin.protocol !== 'http:' || !['127.0.0.1', 'localhost', '[::1]'].includes(origin.hostname) || origin.pathname !== '/' || origin.username || origin.password || origin.search || origin.hash) {
    throw new Error('This tutorial only accepts a loopback HTTP origin.');
  }
  const request = async (path, options = {}) => {
    const response = await fetch(new URL(path, origin), { ...options, redirect: 'error', signal: AbortSignal.timeout(5000) });
    if (!response.ok) throw new Error(`Mock returned HTTP ${response.status}`);
    return response.json();
  };
  const created = await request('/v1/monitor-runs', {
    method: 'POST',
    headers: { 'content-type': 'application/json', 'idempotency-key': randomUUID() },
    body: JSON.stringify({ capability_id: 'observe.ai_answer', prompts: ['Which tools help teams understand AI search visibility?'], interaction_mode: 'search', turnaround_class: 'best_effort' })
  });
  if (!/^job_[a-z0-9_]+$/.test(created.id)) throw new Error('Invalid Job ID');
  const trace = [created.status];
  let job = created;
  for (let attempt = 0; attempt < 8 && job.status !== 'succeeded'; attempt++) {
    await delay(100);
    job = await request(`/v1/jobs/${encodeURIComponent(created.id)}`);
    trace.push(job.status);
    if (['failed', 'cancelled', 'finalized_partial'].includes(job.status)) throw new Error(`Job ended with ${job.status}`);
  }
  if (job.status !== 'succeeded' || job.result_state !== 'complete') throw new Error('Mock did not finish within the bounded polling window');
  const items = await request(`/v1/jobs/${encodeURIComponent(job.id)}/items`);
  if (!Array.isArray(items.data) || items.data.length !== job.progress.completed_items || items.data.some((item) => item.job_id !== job.id)) throw new Error('Observations do not match the completed Job');
  return { source: 'local-mock', job_id: job.id, status_trace: trace, result_state: job.result_state, observation_count: items.data.length, answer: items.data[0]?.answer.text };
}

if (process.argv[1] && pathToFileURL(resolve(process.argv[1])).href === import.meta.url) {
  runTutorial(process.argv[2]).then((result) => console.log(JSON.stringify(result, null, 2))).catch((error) => { console.error(error.message); process.exitCode = 1; });
}

Troubleshooting and next steps

On connection failure, check Mock is still running and the ports match. Use another free local port when necessary; do not stop an unfamiliar service. Mock supports string questions, search and best_effort only. Other configurations are rejected explicitly. Generation and publishing are not simulated. Up to 100 tasks are kept; restarting clears the fictional data.

job_demo_* are fixed workbench fixtures and do not advance through polling. This tutorial creates job_mock_* tasks. Stop Mock with Ctrl+C when done. For a live API, read authentication, capabilities, partial results and error handling. Do not use this local-only tutorial script as a production client.

Download the same example script