/ blockchain.test.js
blockchain.test.js
  1  const Blockchain = require("./blockchain");
  2  const Block = require("./block");
  3  const cryptoHash = require("./crypto-hash");
  4  
  5  describe("Blockchain", () => {
  6    let blockchain, newChain, originalChain;
  7  
  8    beforeEach(() => {
  9      blockchain = new Blockchain();
 10      newChain = new Blockchain();
 11  
 12      originalChain = blockchain.chain;
 13    });
 14  
 15    it("contains a `chain` Array instance", () => {
 16      expect(blockchain.chain instanceof Array).toBe(true);
 17    });
 18  
 19    it("starts with the genesis block", () => {
 20      expect(blockchain.chain[0]).toEqual(Block.genesis());
 21    });
 22  
 23    it("adds a new block to the chain", () => {
 24      const newData = "new data";
 25      blockchain.addBlock({ data: newData });
 26  
 27      expect(blockchain.chain[blockchain.chain.length - 1].data).toEqual(newData);
 28    });
 29  
 30    describe("isValidChain()", () => {
 31      describe("when the chain does not start with the genesis block", () => {
 32        it("returns false", () => {
 33          blockchain.chain[0] = { data: "fake-genesis" };
 34          expect(Blockchain.isValidChain(blockchain.chain)).toBe(false);
 35        });
 36  
 37        describe("when the chain starts with the genesis block and has multiple blocks", () => {
 38          beforeEach(() => {
 39            blockchain.addBlock({ data: "test" });
 40            blockchain.addBlock({ data: "test1" });
 41            blockchain.addBlock({ data: "test2" });
 42          });
 43  
 44          describe("and a lasthash reference has change", () => {
 45            it("returns false", () => {
 46              blockchain.chain[2].lastHash = "broken-lastHash";
 47  
 48              expect(Blockchain.isValidChain(blockchain.chain)).toBe(false);
 49            });
 50            describe("and the chain contains a block with an invalid field", () => {
 51              it("returns false", () => {
 52                blockchain.chain[2].data = "evil-data";
 53  
 54                expect(Blockchain.isValidChain(blockchain.chain)).toBe(false);
 55              });
 56            });
 57  
 58            describe("and the chain contains a block with jumped difficulty", () => {
 59              it("returns false", () => {
 60                const lastBlock = blockchain.chain[blockchain.chain.length - 1];
 61  
 62                const lastHash = lastBlock.hash;
 63                const timestamp = Date.now();
 64                const nonce = 0;
 65                const data = [];
 66  
 67                const difficulty = lastBlock.difficulty - 3;
 68  
 69                const hash = cryptoHash(
 70                  timestamp,
 71                  lastHash,
 72                  difficulty,
 73                  nonce,
 74                  data
 75                );
 76  
 77                const badBlock = new Block({
 78                  timestamp,
 79                  lastHash,
 80                  hash,
 81                  nonce,
 82                  difficulty,
 83                  data,
 84                });
 85  
 86                blockchain.chain.push(badBlock);
 87                expect(Blockchain.isValidChain(blockchain.chain)).toBe(false);
 88              });
 89            });
 90  
 91            describe("and the chain does not contain any invalid blocks", () => {
 92              it("returns true", () => {
 93                console.log(blockchain.chain);
 94                expect(Blockchain.isValidChain(blockchain.chain)).toBe(true);
 95              });
 96            });
 97          });
 98        });
 99      });
100    });
101  
102    describe("replaceChain()", () => {
103      let errorMock, logMock;
104      beforeEach(() => {
105        errorMock = jest.fn();
106        logMock = jest.fn();
107  
108        global.console.error = errorMock;
109        global.console.log = logMock;
110      });
111  
112      describe("when the new chain is not longer", () => {
113        beforeEach(() => {
114          newChain.chain[0] = { new: "chain" };
115  
116          blockchain.replaceChain(newChain.chain);
117        });
118  
119        it("does not replace the chain", () => {
120          expect(blockchain.chain).toEqual(originalChain);
121        });
122  
123        it("logs an error", () => {
124          expect(errorMock).toHaveBeenCalled();
125        });
126      });
127  
128      describe("when the new chain is longer", () => {
129        beforeEach(() => {
130          newChain.addBlock({ data: "test" });
131          newChain.addBlock({ data: "test1" });
132          newChain.addBlock({ data: "test2" });
133        });
134        describe("and the chain is invalid", () => {
135          beforeEach(() => {
136            newChain.chain[2].hash = "fake-hash";
137  
138            blockchain.replaceChain(newChain.chain);
139          });
140          it("does not replace the chain", () => {
141            expect(blockchain.chain).toEqual(originalChain);
142          });
143  
144          it("logs an error", () => {
145            expect(errorMock).toHaveBeenCalled();
146          });
147        });
148  
149        describe("and the chain is valid", () => {
150          beforeEach(() => {
151            blockchain.replaceChain(newChain.chain);
152          });
153  
154          it("replaces the chain", () => {
155            expect(blockchain.chain).toEqual(newChain.chain);
156          });
157  
158          it("logs about the chain replacement", () => {
159            expect(logMock).toHaveBeenCalled();
160          });
161        });
162      });
163    });
164  });