# Exercise 2: Model the Instance Lifecycle State Machine

```
STEP 1. Save the JavaScript below as `instance_lifecycle.js`.
STEP 2. Implement `transition(state, action)`:
          - Valid actions: "stop", "start", "terminate".
          - "stop" is only legal from "running" -> goes to "stopped"
            (model it as instantaneous; ignore the "stopping" substate).
          - "start" is only legal from "stopped" -> goes to "running".
          - "terminate" is legal from "running" OR "stopped" -> goes to
            "terminated", and once "terminated" NO further transition is
            ever legal again (not even another terminate).
          - Any other (state, action) pair is illegal: return the
            SAME state unchanged (no crash, no invalid transition).
STEP 3. Implement `survivesStop(hasElasticIp, isInstanceStoreBacked)`:
          - Per theory section 5, does a public IP survive a stop?
            Only if there's an Elastic IP attached.
          - Does instance-store data survive a stop? Never (theory
            section 5) — instance-store is lost on stop, not just
            terminate.
          - Return { publicIpSurvives: boolean, dataSurvives: boolean }.
STEP 4. Run `node instance_lifecycle.js` and get every line to print PASS.
```

```js
// instance_lifecycle.js

function transition(state, action) {
  // TODO: implement per the rules in STEP 2
  return state;
}

function survivesStop(hasElasticIp, isInstanceStoreBacked) {
  // TODO: implement per the rules in STEP 3
  return { publicIpSurvives: false, dataSurvives: false };
}

// --- self-check: do not modify below this line ---

const transitionTests = [
  { state: "running", action: "stop", expected: "stopped" },
  { state: "stopped", action: "start", expected: "running" },
  { state: "running", action: "terminate", expected: "terminated" },
  { state: "stopped", action: "terminate", expected: "terminated" },
  { state: "terminated", action: "start", expected: "terminated" },
  { state: "terminated", action: "terminate", expected: "terminated" },
  { state: "stopped", action: "stop", expected: "stopped" },
  { state: "running", action: "start", expected: "running" },
];

let failures = 0;
for (const t of transitionTests) {
  const result = transition(t.state, t.action);
  const pass = result === t.expected;
  if (!pass) failures++;
  console.log(`${pass ? "PASS" : "FAIL"} - transition(${t.state}, ${t.action}) -> expected ${t.expected}, got ${result}`);
}

const survivalTests = [
  { hasElasticIp: true, isInstanceStoreBacked: false, expected: { publicIpSurvives: true, dataSurvives: true } },
  { hasElasticIp: false, isInstanceStoreBacked: false, expected: { publicIpSurvives: false, dataSurvives: true } },
  { hasElasticIp: true, isInstanceStoreBacked: true, expected: { publicIpSurvives: true, dataSurvives: false } },
  { hasElasticIp: false, isInstanceStoreBacked: true, expected: { publicIpSurvives: false, dataSurvives: false } },
];

for (const t of survivalTests) {
  const result = survivesStop(t.hasElasticIp, t.isInstanceStoreBacked);
  const pass = result.publicIpSurvives === t.expected.publicIpSurvives && result.dataSurvives === t.expected.dataSurvives;
  if (!pass) failures++;
  console.log(`${pass ? "PASS" : "FAIL"} - survivesStop(elasticIp=${t.hasElasticIp}, instanceStore=${t.isInstanceStoreBacked})`);
}

console.log(failures === 0 ? "\nAll tests passed." : `\n${failures} test(s) failed.`);
```
