<p>sinon.restore() &amp;&amp; sandbox.restore() рдирдХрд▓реА рдмрд╣рд╛рд▓ рдирд╣реАрдВ рдХрд░ рд░рд╣рд╛ рд╣реИ</p>

рдХреЛ рдирд┐рд░реНрдорд┐рдд 23 рдЬреБрд▓ре░ 2019  ┬╖  16рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ  ┬╖  рд╕реНрд░реЛрдд: sinonjs/sinon

рдмрдЧ рдХрд╛ рд╡рд░реНрдгрди рдХрд░реЗрдВ
рдирдХрд▓реА sinon.restore() (рди рд╣реА resetHistory , resetBehavior , рдФрд░ рди рд╣реА reset ) рдХреЛ рд╕рд╛рдлрд╝ рдирд╣реАрдВ рдХрд┐рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ред рдлрд┐рд░ рдореИрдВрдиреЗ рдПрдХ рдирдпрд╛ рд╕реИрдВрдбрдмреЙрдХреНрд╕ рдмрдирд╛рдиреЗ рдФрд░ рд╕реИрдВрдбрдмреЙрдХреНрд╕ рдХреЛ рд╕рд╛рдлрд╝ рдХрд░рдиреЗ рдХрд╛ рдкреНрд░рдпрд╛рд╕ рдХрд┐рдпрд╛ред рдХреЛрдИ рдкрд╛рдБрд╕рд╛ рдирд╣реАрдВред рдХреНрдпрд╛ рдпрд╣ рдЬрд╛рдирдмреВрдЭрдХрд░ рд╣реИ?

рдкреНрд░рдЬрдирди рдХрд░рдирд╛

it("Demonstrates how to restore a fake", () => {
  let sandbox = sinon.createSandbox();
  let thing = sandbox.fake();
  thing("Hi");
  expect(thing.calledOnce).to.be.true;
  expect(thing.lastArg).to.equal("Hi");
  sandbox.restore();
  sandbox.resetHistory();
  sandbox.resetBehavior();
  sandbox.reset();
  expect(thing.calledOnce).to.be.false; // Fails. thing.calledOnce === true
  expect(thing.lastArg).to.be.undefined; // Fails if above commented out. thing.lastArg === "Hi"
});

рдЕрдкреЗрдХреНрд╖рд┐рдд рд╡реНрдпрд╡рд╣рд╛рд░
рдореБрдЭреЗ рдЙрдореНрдореАрдж рдереА рдХрд┐ thing.lastArg рдпрд╛ рддреЛ undefined рдпрд╛ null ред рдпрд╣ рдореБрдЭреЗ "Hi"

рд╕реНрдХреНрд░реАрдирд╢реЙрдЯ
рдПрди/рдП

рдкреНрд░рд╕рдВрдЧ (рдХреГрдкрдпрд╛ рдирд┐рдореНрдирд▓рд┐рдЦрд┐рдд рдЬрд╛рдирдХрд╛рд░реА рдХреЛ рдкреВрд░рд╛ рдХрд░реЗрдВ):

  • рдкреБрд╕реНрддрдХрд╛рд▓рдп рд╕рдВрд╕реНрдХрд░рдг: рд╕рд┐рдиреЙрди ^7.3.2
  • рдкрд░реНрдпрд╛рд╡рд░рдг: рдиреЛрдб 10.16.0
  • рдЙрджрд╛рд╣рд░рдг рдпреВрдЖрд░рдПрд▓: рд▓рд╛рдЧреВ рдирд╣реАрдВ
  • рдЕрдиреНрдп рдкреБрд╕реНрддрдХрд╛рд▓рдп рдЬрд┐рдирдХрд╛ рдЖрдк рдЙрдкрдпреЛрдЧ рдХрд░ рд░рд╣реЗ рд╣реИрдВ: рдореЛрдЪрд╛, рдЪрд╛рдИ

рдЕрддрд┐рд░рд┐рдХреНрдд рд╕рдВрджрд░реНрдн
рдмрд╕ рд╕рд┐рдиреЙрди рдХреА рдХреНрд╖рдорддрд╛рдУрдВ рдХрд╛ рдкрд░реАрдХреНрд╖рдг рдХрд░ рд░рд╣рд╛ рд╣реВрдВ рдХреНрдпреЛрдВрдХрд┐ рдореИрдВ рдЗрд╕рдХрд╛ рдЙрдкрдпреЛрдЧ рдмреИрдХрдПрдВрдб рдХрд╛ рдкрд░реАрдХреНрд╖рдг рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдХрд░рддрд╛ рд╣реВрдВред

рд╕рдмрд╕реЗ рдЙрдкрдпреЛрдЧреА рдЯрд┐рдкреНрдкрдгреА

рдХрд╛рдлреА рдЙрдЪрд┐рдд - рд╕реНрдкрд╖реНрдЯреАрдХрд░рдг рдХреЗ рд▓рд┐рдП рдзрдиреНрдпрд╡рд╛рджред

рд╢реБрдХреНрд░, 19 рдлрд░рд╡рд░реА, 2021 рдХреЛ рд╕реБрдмрд╣ 7:02 рдмрдЬреЗ рдХрд╛рд░реНрд▓-рдПрд░рд┐рдХ рдХреЛрдкреНрд╕реЗрдВрдЧ рдиреЛрдЯрд┐рдлрд┐рдХреЗрд╢рди @github.com
рд▓рд┐рдЦрд╛ рдерд╛:

@scottpatrickwright https://github.com/scottpatrickwright рдпрд╣ рдмрдВрдж рд╣реИ
рдХреНрдпреЛрдВрдХрд┐ рдХреЛрдИ рдмрдЧ рдирд╣реАрдВ рд╣реИ, рдЬреИрд╕рд╛ рдХрд┐ рдзрд╛рдЧреЗ рд╕реЗ рд╕реНрдкрд╖реНрдЯ рд╣реЛрдирд╛ рдЪрд╛рд╣рд┐рдПред рдпрд╣ рдмрд╕ рдЧрдпрд╛ рдерд╛
рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рддреНрд░реБрдЯрд┐, рдЬреИрд╕рд╛ рдХрд┐ рдЖрдкрдХреЗ рдорд╛рдорд▓реЗ рдореЗрдВ рд╣реИ: рдпрд╣ рд╡рд░реНрдгрд┐рдд рдФрд░ рдЗрдЪреНрдЫрд┐рдд рдХреЗ рд░реВрдк рдореЗрдВ рдХрд╛рдо рдХрд░рддрд╛ рд╣реИ :-)

рд╕рд┐рдиреЙрди рд╕реИрдВрдбрдмреЙрдХреНрд╕ рдЙрди рдЪреАрдЬреЛрдВ рдХреЛ рд╕рд╛рдл рдХрд░ рд╕рдХрддрд╛ рд╣реИ рдЬрд┐рдирдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рд╡рд╣ рдЬрд╛рдирддрд╛ рд╣реИред рдЗрд╕рдХрд╛ рдорддрд▓рдм рд╣реИ рдХрд┐ рд╕рд┐рдиреЛрди рд╣реИ
рдЙрди рд╡рд╕реНрддреБрдУрдВ рдХрд╛ рдЬреНрдЮрд╛рди рдкреНрд░рд╛рдкреНрдд рдХрд░рдирд╛ рдЬрд┐рдирдХреЗ рд╕рд╛рде рдпрд╣ рдмрд╛рддрдЪреАрдд рдХрд░рддрд╛ рд╣реИ рдареВрдВрда рдЖрдк
рдКрдкрд░ рд╡рд░реНрдгрд┐рдд рд╕реНрдЯреИрдВрдбрдЕрд▓реЛрди рдмрдирд╛рдпрд╛ рдЧрдпрд╛ рд╣реИред рд╕рд┐рдиреЙрди рдХреЛ рдЗрдирдореЗрдВ рд╕реЗ рдХрд┐рд╕реА рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдЕрд╡рдЧрдд рдирд╣реАрдВ рдХрд░рд╛рдпрд╛ рдЧрдпрд╛ рд╣реИ
рдЬрд┐рди рд╡рд╕реНрддреБрдУрдВ рдХреЛ рдЖрдк рдЗрд╕реЗ рдЕрд╕рд╛рдЗрди рдХрд░рддреЗ рд╣реИрдВред рд╡рд╣ рджреВрд╕рд░реЗ рдЙрджрд╛рд╣рд░рдг рдореЗрдВ рдмрджрд▓ рдЬрд╛рддрд╛ рд╣реИред рдЖрдк рдРрд╕рд╛ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ
рдЗрд╕реЗ рдкреНрд░рд╛рдкреНрдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП sinon.replace(obj, fieldname, fake) рднреА рдХрд░реЗрдВред рдпрд╣ рд╕рд┐рд░реНрдл
рдЬрд╛рд╡рд╛рд╕реНрдХреНрд░рд┐рдкреНрдЯ

-
рдЖрдк рдЗрд╕реЗ рдкреНрд░рд╛рдкреНрдд рдХрд░ рд░рд╣реЗ рд╣реИрдВ рдХреНрдпреЛрдВрдХрд┐ рдЖрдкрдХрд╛ рдЙрд▓реНрд▓реЗрдЦ рдХрд┐рдпрд╛ рдЧрдпрд╛ рдерд╛ред
рдЗрд╕ рдИрдореЗрд▓ рдХрд╛ рд╕реАрдзреЗ рдЙрддреНрддрд░ рджреЗрдВ, рдЗрд╕реЗ GitHub рдкрд░ рджреЗрдЦреЗрдВ
https://github.com/sinonjs/sinon/issues/2065#issuecomment-782032440 , рдпрд╛
рд╕рджрд╕реНрдпрддрд╛ рд╕рдорд╛рдкреНрдд
https://github.com/notifications/unsubscribe-auth/AAKKRTFVMGEYVIB5GSIDXKTS7ZHMRANCNFSM4IGBASQQ
.

рд╕рднреА 16 рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ

рд╡реИрд╕реЗ рднреА, рдпрд╣ рдПрдХ рдмрдЧ рдЬреИрд╕рд╛ рджрд┐рдЦрддрд╛ рд╣реИ, рд▓реЗрдХрд┐рди рд╕рдЪ рдХрд╣реВрдВ рддреЛ, рдореИрдВ рдЗрд╕ рдмрд╛рд░реЗ рдореЗрдВ рдкреВрд░реА рддрд░рд╣ рд╕реЗ рдирд┐рд╢реНрдЪрд┐рдд рдирд╣реАрдВ рд╣реВрдВред рдореБрдЭреЗ рдпрд╛рдж рд╣реИ рдХрд┐ рдирдХрд▓реА рдкрд░ рдПрдХ рдорд╣рддреНрд╡рдкреВрд░реНрдг рдмрд┐рдВрджреБ рдпрд╣ рд╣реИ рдХрд┐ рдЙрдиреНрд╣реЗрдВ рдХреБрдЫ рдЕрд░реНрдереЛрдВ рдореЗрдВ рдЕрдкрд░рд┐рд╡рд░реНрддрдиреАрдп рдорд╛рдирд╛ рдЬрд╛рддрд╛ рдерд╛, рд▓реЗрдХрд┐рди рдЪреВрдВрдХрд┐ рдЙрдирдХреЗ рдкрд╛рд╕ рд░рд╛рдЬреНрдп рд╣реИ (рдпрд╛рдиреА рд╡реЗ рдЬрд╛рдирддреЗ рд╣реИрдВ рдХрд┐ рдЙрдиреНрд╣реЗрдВ рдмреБрд▓рд╛рдпрд╛ рдЧрдпрд╛ рд╣реИ), рдХрд┐ рдЕрдкрд░рд┐рд╡рд░реНрддрдиреАрдпрддрд╛ рдХреЗрд╡рд▓ рдареВрдВрдарджрд╛рд░ рд╡реНрдпрд╡рд╣рд╛рд░ рддрдХ рд╣реА рд╕реАрдорд┐рдд рд╣реЛрдиреА рдЪрд╛рд╣рд┐рдП, рдореБрдЭреЗ рд▓рдЧрддрд╛ рд╣реИред

@mrodrick рдЗрд╕рдХрд╛ рдЙрддреНрддрд░ рджреЗрдиреЗ рдХреЗ рд▓рд┐рдП рд╕рд╣реА рд╡реНрдпрдХреНрддрд┐ рд╣реЛ рд╕рдХрддрд╛ рд╣реИ ...

рдпрджрд┐ рдпрд╣ рд╕рд╣реА рд╡реНрдпрд╡рд╣рд╛рд░ рд╣реИ, рддреЛ рд╣рдореЗрдВ рдХрдо рд╕реЗ рдХрдо рдбреЙрдХреНрд╕ рдХреЛ рдЕрдкрдбреЗрдЯ рдХрд░рдирд╛ рдЪрд╛рд╣рд┐рдПред

@fatso83 рдмрд╣реБрдд рд╕рд░рд╛рд╣рдирд╛ рдХреА! .restore рджрд╕реНрддрд╛рд╡реЗрдЬрд╝ рдореЗрдВ рдирдХрд▓реА рдХрд╛ рдЙрд▓реНрд▓реЗрдЦ рдХрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИ:

image

рдпрд╣рд╛рдБ рд╕реЗ ред

рдЗрд╕ рдмрдЧ рд╕реЗ рдмрд╕ рдереЛрдбрд╝рд╛ рд╕рд╛ рд╣реА рдерд╛, рдЗрд╕рд▓рд┐рдП рдЗрд╕реЗ рдареАрдХ рдХрд░рдирд╛ рдЕрдЪреНрдЫрд╛ рд╣реЛрдЧрд╛ред

рдЕрдЧрд░ рдЕрдЧрд▓реЗ рдХреБрдЫ рджрд┐рдиреЛрдВ рдореЗрдВ рдХреЛрдИ рдФрд░ рдЗрд╕рдХреЗ рд▓рд┐рдП рдкреАрдЖрд░ рдирд╣реАрдВ рдХрд░рддрд╛ рд╣реИ, рддреЛ рдореИрдВ рджреЗрдЦ рд╕рдХрддрд╛ рд╣реВрдВ рдХрд┐ рдореБрдЭреЗ рдЗрд╕реЗ рдареАрдХ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд╕рдордп рдорд┐рд▓ рд╕рдХрддрд╛ рд╣реИ рдпрд╛ рдирд╣реАрдВ

рдореИрдВрдиреЗ рдЕрдм рдЗрд╕ рдкрд░ рдзреНрдпрд╛рди рджрд┐рдпрд╛ рдФрд░ рдбреЙрдХреНрд╕ рдереЛрдбрд╝реЗ рднреНрд░рд╛рдордХ рд╣реИрдВ, рдЬреИрд╕рд╛ рдХрд┐ рдКрдкрд░ рджрд┐рдпрд╛ рдЧрдпрд╛ рдкрд░реАрдХреНрд╖рдг рд╣реИред рдРрд╕рд╛ рд▓рдЧрддрд╛ рд╣реИ рдХрд┐ рдпрд╣ рд╡рд╛рд╕реНрддрд╡ рдореЗрдВ (рд▓рдЧрднрдЧ) рдХреНрдпрд╛ рдХрд╣рддрд╛ рд╣реИ, рд▓реЗрдХрд┐рди рдЖрдЗрдП рдкрд╣рд▓реЗ рдХреБрдЫ рд╢рд░реНрддреЛрдВ рдХреЛ рдкрд░рд┐рднрд╛рд╖рд┐рдд рдХрд░реЗрдВ:

рдкреБрдирд░реНрд╕реНрдерд╛рдкрд┐рдд рдХрд░реЗрдВ - рд╕реИрдВрдбрдмреЙрдХреНрд╕ рдХреЛ рдкреБрдирд░реНрд╕реНрдерд╛рдкрд┐рдд рдХрд░рдиреЗ рдХрд╛ рдЕрд░реНрде рд╣реИ рд╕рднреА рд╕реНрдЯрдм рдХрд┐рдП рдЧрдП рдХрд╛рд░реНрдпреЛрдВ рдХреЛ рдЙрдирдХреА рдореВрд▓ рд╕реНрдерд┐рддрд┐ рдореЗрдВ рдкреБрдирд░реНрд╕реНрдерд╛рдкрд┐рдд рдХрд░рдирд╛, рдЬрд┐рд╕рдХрд╛ рдЕрд░реНрде рд╣реИ рдХрд┐ рдкреНрд░рддреНрдпреЗрдХ рдСрдмреНрдЬреЗрдХреНрдЯ рд╕реЗ рд╕реНрдЯрдмреНрд╕ рд╣рдЯрд╛ рджрд┐рдП рдЬрд╛рддреЗ рд╣реИрдВ рдпрджрд┐ рд╡реЗ рдореВрд▓ рдХреЛ рдУрд╡рд░рд░рд╛рдЗрдЯ рдХрд░рддреЗ рд╣реИрдВред
рд░реАрд╕реЗрдЯ - рд░реАрд╕реЗрдЯ рдХрд░рдиреЗ рдХрд╛ рдорддрд▓рдм рд╣реИ рдХрд┐ рдмрдирд╛рдП рдЧрдП рдкреНрд░рддреНрдпреЗрдХ рдирдХрд▓реА рдХреА рд╕реНрдерд┐рддрд┐ рд╕рд╛рдл рд╣реЛ рдЧрдИ рд╣реИ, рд▓реЗрдХрд┐рди рд╡реЗ рдЕрднреА рднреА рд╡рд╕реНрддреБрдУрдВ рдкрд░ рдореМрдЬреВрдж рд╣реИрдВред

рдЙрдкрд░реЛрдХреНрдд рдкрд░реАрдХреНрд╖рдг рдХреЗ рд╕рд╛рде рд╕рдорд╕реНрдпрд╛ рдпрд╣ рд╣реИ рдХрд┐ рдпрд╣

  1. рд╕реИрдВрдбрдмреЙрдХреНрд╕ рд╕реЗ рд╕рднреА рдирдХрд▓реА рд╣рдЯрд╛ рджреЗрддрд╛ рд╣реИ
  2. рдлрд┐рд░ рд╕реИрдВрдбрдмреЙрдХреНрд╕ (рдЕрдм рдЦрд╛рд▓реА) рдореЗрдВ рдирдХрд▓реА рдХреЗ рд╕рдВрдЧреНрд░рд╣ рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ рдЬрд╛рдиреЗ рдХреЗ рд▓рд┐рдП рдЖрдЧреЗ рдмрдврд╝рддрд╛ рд╣реИ рдФрд░ рдирдХрд▓реА рдХреА (рдЦрд╛рд▓реА) рд╕реВрдЪреА рдХреЛ рд░реАрд╕реЗрдЯ рдХрд░рдиреЗ рдХрд╛ рдкреНрд░рдпрд╛рд╕ рдХрд░рддрд╛ рд╣реИ

рдЗрд╕рдХрд╛ рдХреЛрдИ рдорддрд▓рдм рдирд╣реАрдВ рд╣реИ :-)

рдореИрдВрдиреЗ рдкрд░реАрдХреНрд╖рдг рдХрд┐рдпрд╛ рд╣реИ рдФрд░ reset рдФрд░ restore рдХреЙрд▓ рдЙрд╕реА рддрд░рд╣ рдХрд╛рдо рдХрд░рддреЗ рд╣реИрдВ рдЬреИрд╕реЗ рдЙрдиреНрд╣реЗрдВ рдХрд░рдирд╛ рдЪрд╛рд╣рд┐рдПред рд▓рдЧрднрдЧ ... reset calledOnce рдХреА рд╕реНрдерд┐рддрд┐ рдХреЛ рд╕рд╛рдл рдХрд░рддрд╛ рд╣реИ, рд▓реЗрдХрд┐рди lastArgument рдХреЛ рд╕рд╛рдл рдирд╣реАрдВ рдХрд┐рдпрд╛ рдЬрд╛ рд░рд╣рд╛ рд╣реИред рдпрд╣ рджреЗрдЦрдиреЗ рдХреЗ рд▓рд┐рдП рдереЛрдбрд╝рд╛ рдФрд░ рдЬрд╛рдВрдЪрдирд╛ рдХрд┐ рдХреНрдпрд╛ рдореИрдВрдиреЗ рдХреБрдЫ рд╡рд┐рд╡рд░рдг рдЦреЛ рджрд┐рдпрд╛ рд╣реИред

рдЗрд╕реЗ рдкреНрд░рддрд┐рд▓рд┐рдкрд┐ рдкреНрд░рд╕реНрддреБрдд рдХрд░рдиреЗ рдпреЛрдЧреНрдп рдирд╣реАрдВ рдХреЗ рд░реВрдк рдореЗрдВ рдмрдВрдж рдХрд░рдирд╛, рд▓реЗрдХрд┐рди fake.lastArg рдмрдЧ рдкрд░ рдПрдХ рдирдпрд╛ рдЕрдВрдХ рдЦреЛрд▓рдирд╛ред

рд╕рддреНрдпрд╛рдкрд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП: https://runkit.com/fatso83/sinon-issue-2065

рдореИрдВ рдкреБрд╖реНрдЯрд┐ рдХрд░ рд╕рдХрддрд╛ рд╣реВрдВ рдХрд┐ рдпрд╣ рдмрдЧ рдореЗрд░реЗ рд╕рд╛рде рднреА рд╣реЛ рд░рд╣рд╛ рд╣реИ, рдореЗрд░реЗ рдкрд╛рд╕ рдПрдХ рдкрд░реАрдХреНрд╖рдг рдкрд░рд┐рджреГрд╢реНрдп рд╣реИ рдЬрд┐рд╕рдХреЗ рд▓рд┐рдП рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдХреЛ рдмрд░рдХрд░рд╛рд░ рд░рдЦрдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ, рд▓реЗрдХрд┐рди рдлрд╝рдВрдХреНрд╢рди рдкрд┐рдЫрд▓реЗ рдкрд░реАрдХреНрд╖рдг рд╕реЗ рд╕реНрдЯрдм рдкрд░рд┐рдгрд╛рдо рд░рдЦрддрд╛ рд╣реИ, рдЬрд┐рд╕рдХрд╛ рдЕрд░реНрде рд╣реИ рдХрд┐ sandbox.restore() рд╡рд╣ рдирд╣реАрдВ рдХрд░ рд░рд╣рд╛ рд╣реИ рдЬреЛ рдХрд░рдиреЗ рдХрд╛ рдЗрд░рд╛рджрд╛ рд╣реИред

рдпрд╣ рдореЗрд░реЗ рд╕рд╛рде рд╕рдВрд╕реНрдХрд░рдг 7.5.0

@ oscarr-reyes рдХреНрдпрд╛ рдЖрдк рджреЗрдЦ рд╕рдХрддреЗ рд╣реИрдВ рдХрд┐ https://github.com/sinonjs/sinon/commit/54df7f7000a9db2dd05319daa49518f3cd5a5dd7 рдЗрд╕реЗ рдЖрдкрдХреЗ рд▓рд┐рдП рдареАрдХ рдХрд░рддрд╛ рд╣реИ рдпрд╛ рдирд╣реАрдВ? рдореБрдЭреЗ рд▓рдЧрддрд╛ рд╣реИ рдХрд┐ рдЗрд╕рдиреЗ рдЗрд╕реЗ рдЕрднреА рддрдХ рдПрдХ рдирдИ рд░рд┐рд▓реАрдЬрд╝ рдореЗрдВ рдирд╣реАрдВ рдмрдирд╛рдпрд╛ рд╣реИред

@ oscarr-reyes рдХреНрдпрд╛ рдЖрдк рдПрдХ рдкреНрд░рддрд┐рд▓рд┐рдкрд┐ рдкреНрд░рд╕реНрддреБрдд рдХрд░рдиреЗ рдпреЛрдЧреНрдп рдЙрджрд╛рд╣рд░рдг рдмрдирд╛рдиреЗ рдореЗрдВ рд╕рдХреНрд╖рдо рд╣реИрдВ? рд╣рдордиреЗ рдЕрднреА рддрдХ рд╡рд╛рд╕реНрддрд╡ рдореЗрдВ рдЗрд╕ рдмрдЧ рдХреЛ рдирд╣реАрдВ рджреЗрдЦрд╛ рд╣реИред рдпрджрд┐ рдЖрдк рдКрдкрд░ рджрд┐рдП рдЧрдП рдзрд╛рдЧреЗ рдФрд░ рдореЗрд░реА рд╡реНрдпрд╛рдЦреНрдпрд╛ рдХреЛ рджреЗрдЦрддреЗ рд╣реИрдВ, рддреЛ рдЖрдк рджреЗрдЦреЗрдВрдЧреЗ рдХрд┐ рд╣рдореЗрдВ рдХреЗрд╡рд▓ рдПрдХ рд╣реА рдмрдЧ рдорд┐рд▓рд╛ рд╣реИ рдЬреЛ рдПрдХ рдзреНрд╡рдЬ рдХреЛ рд╕рд╛рдл рдирд╣реАрдВ рдХрд┐рдпрд╛ рдЧрдпрд╛ рдерд╛ред рдмрд╛рдХреА рд╕рдм рдХрд╛рдо рдХрд░рддрд╛ рд╣реИред рдЗрд╕рд▓рд┐рдП рдЬрдм рддрдХ рд╣рдо рд╡рд╛рд╕реНрддрд╡ рдореЗрдВ рдХреБрдЫ рдкреНрд░рдорд╛рдг рдирд╣реАрдВ рджреЗрдЦрддреЗ рд╣реИрдВ, рддрдм рддрдХ рдХреЛрдИ рдмрдЧ рдирд╣реАрдВ рд╣реИ (lastArg рдзреНрд╡рдЬ рдХреЗ рдЕрд▓рд╛рд╡рд╛)ред

рдореБрдЭреЗ рдПрдХ рд╣реА рдореБрджреНрджреЗ рдХрд╛ рд╕рд╛рдордирд╛ рдХрд░рдирд╛ рдкрдбрд╝ рд░рд╣рд╛ рд╣реИред

рдореИрдВ рдЕрдкрдиреЗ "ValidateCaptcha" рдорд┐рдбрд▓рд╡реЗрдпрд░ рдХреЛ рд╕реИрдВрдбрдмреЙрдХреНрд╕рд┐рдВрдЧ рдХрд░ рд░рд╣рд╛ рд╣реВрдВ рдФрд░ рдЬрдм рдореИрдВ рдЗрд╕реЗ рдкреБрдирд░реНрд╕реНрдерд╛рдкрд┐рдд рдХрд░рддрд╛ рд╣реВрдВ рддреЛ рдпрд╣ рд╕реНрдЯрдм рдЬрд╛рд░реА рдХрд░рдирд╛ рдЬрд╛рд░реА рд░рдЦрддрд╛ рд╣реИред

/src/middlewares/captcha.js

const request = require("got");

const validateCaptcha = async (req, res, next) => {
    // if (process.env.NODE_ENV !== "production") return next();

    const captcha = req.body["g-recaptcha-response"] || req.body.g_recaptcha_response;

    // g-recaptcha-response is the key that browser will generate upon form submit.
    // if its blank or null means user has not selected the captcha, so return the error.
    if (captcha === undefined || captcha === "" || captcha === null)
        return res
            .status(400)
            .json({ message: "Please, verify that you are not a robot" });

    // Put your secret key here.
    const secretKey = process.env.GOOGLE_CAPTCHA_API_SECRET_KEY;

    // req.connection.remoteAddress will provide IP address of connected user.
    const verificationUrl = `https://www.google.com/recaptcha/api/siteverify?secret=${secretKey}&response=${captcha}`;

    try {

        // Hitting GET request to the URL, Google will respond with success or error scenario.
        const { body } = await request.post(verificationUrl, { responseType: "json" });

        // Success will be true or false depending upon captcha validation.
        if (body.success !== undefined && !body.success)
            return res
                .status(400)
                .json({ message: "Captcha validation error" });
    } catch (error) {
        return res
            .status(503)
            .json({ message: "Couldn't validate captcha" });
    }

    next();
};

module.exports = {
    validateCaptcha,
};

/test/api/user.spec.js

require("../setup.spec");
const supertest = require("supertest");
const captcha = require("../../src/middlewares/captcha");
const sinon = require("sinon");
let app;
let agent;
let sandbox;

const login = (anAgent, userCredentials) =>
    anAgent
        .post("/api/users/signIn")
        .send(userCredentials);

const logout = (anAgent) =>
    anAgent
        .post("/api/users/signOut");

describe("User endpoints", function () {
    this.beforeAll(async () => {
        sandbox = sinon.createSandbox();
        sandbox
            .stub(captcha, "validateCaptcha")
            .callsFake(async (req, res, next) => {
                console.log("Validate Captcha FAKE");
                return next();
            });
        // sandbox.restore();
    });

    this.beforeEach(async () => {
        app = require("../../src/app");
        agent = supertest.agent(app);
        const sequelize = require("../../src/models");
        await sequelize.sync({ force: true });

        const Customer = require("../../src/models/customer");
        const User = require("../../src/models/user");
        await Customer.create(
            {
                firstName: "Test",
                lastName: "Customer",
                user: {
                    email: "[email protected]",
                    password: "boo",
                },
            },
            {
                include: [User],
            }
        );
    });

    this.afterEach(async () => {
        sandbox.restore();
    });

    it("should create a new user", function (done) {
        agent
            .post("/api/users/signUp")
            .send({
                firstName: "firstName",
                lastName: "lastName",
                email: "[email protected]",
                password: "aPassword",
            })
            .expect(201)
            .end((err, res) => {
                err ? done(err) : done();
            });
    });

    it("should sign in", function (done) {
        login(agent, { email: "[email protected]", password: "boo" })
            .expect(200)
            .end((err, res) => {
                const { message, user } = res.body;
                message.should.be.equal("valid");
                user.should.have.property("email").equal("[email protected]");
                user.should.have.property("customer").have.property("firstName").equal("Test");
                user.should.have.property("customer").have.property("lastName").equal("Customer");

                agent
                    .get("/api/users/me")
                    .expect(200)
                    .end((err, res) => {
                        const user = res.body;
                        user.should.have.property("email").equal("[email protected]");
                        user.should.have.property("customer").have.property("firstName").equal("Test");
                        user.should.have.property("customer").have.property("lastName").equal("Customer");
                        err ? done(err) : done();
                    });
            });
    });

    it("should sign out", async function () {
        await login(agent, { email: "[email protected]", password: "boo" });
        await logout(agent)
            .expect(302)
            .expect("Location", "/");

        return agent
            .get("/api/users/me")
            .expect(401);
    });

    it("should return unauthorized", function (done) {
        agent
            .get("/api/users/me")
            .expect(401)
            .end((err, res) => {
                err ? done(err) : done();
            });
    });
});

рдореИрдВ рдпрд╣ рдЬрд╛рдВрдЪрдиреЗ рдХреЗ рд▓рд┐рдП рдХреНрдпрд╛ рдХрд░ рд░рд╣рд╛ рд╣реВрдВ рдХрд┐ рдпрд╣ рдХрд╛рдо рдирд╣реАрдВ рдХрд░рддрд╛ рд╣реИ:
1) рдкрд╣рд▓реЗ рдЯреЗрд╕реНрдЯ рдХреЗ рд▓рд┐рдП рдЗрд╕рдХрд╛ рдореЙрдХ рдХрд░реЗрдВ
2) рдЗрд╕реЗ рдирд┐рдореНрдирд▓рд┐рдЦрд┐рдд рдХреЗ рд▓рд┐рдП рдкреБрдирд░реНрд╕реНрдерд╛рдкрд┐рдд рдХрд░реЗрдВ

рдкрд╣рд▓реЗ рд╕рднреА рдХреЗ рдЕрдВрджрд░ рдПрдХ рдЯрд┐рдкреНрдкрдгреА рд╣реИ "sandbox.restore ();" рдХрд┐ рдореИрдВрдиреЗ рдХреЛрд╢рд┐рд╢ рдХреА рд╣реИ рдФрд░ рд╡рд╣рд╛рдБ рдпрд╣ рдХрд╛рдо рдХрд┐рдпрд╛... рд▓реЗрдХрд┐рди рдпрд╣ рдмреЗрдХрд╛рд░ рд╣реИ

рдореИрдВ рднреА рдЕрдкрдиреЗ app.js . рдХреЗ рд▓рд┐рдП рд╣рд░ рдмрд╛рд░ рдЖрд╡рд╢реНрдпрдХрддрд╛ рдХрд░ рд░рд╣рд╛ рд╣реВрдБ

рдкрд╛рдВрдЪ рдорд┐рдирдЯ рдкрд╣рд▓реЗ рдореИрдВрдиреЗ рдЗрд╕реЗ рд╕рдВрд╢реЛрдзрд┐рдд рдХрд┐рдпрд╛ рд╣реИ:

require("../setup.spec");
const decache = require("decache");
const supertest = require("supertest");
const sequelize = require("../../src/models");

const sinon = require("sinon");
const sandbox = sinon.createSandbox();
let agent;

const login = (anAgent, userCredentials) =>
    anAgent
        .post("/api/users/signIn")
        .send(userCredentials);

const logout = (anAgent) =>
    anAgent
        .post("/api/users/signOut");

describe("User endpoints", function () {
    this.beforeAll(async () => {

    });

    this.beforeEach(async () => {
        decache("../../src/app");
        decache("../../src/middlewares/captcha");

        const captcha = require("../../src/middlewares/captcha");

        sandbox
            .stub(captcha, "validateCaptcha")
            .callsFake(async (req, res, next) => {
                console.log("Validate Captcha FAKE");
                return next();
            });

        const app = require("../../src/app");
        agent = supertest.agent(app);
        await sequelize.sync({ force: true });


        const Customer = require("../../src/models/customer");
        const User = require("../../src/models/user");
        await Customer.create(
            {
                firstName: "Test",
                lastName: "Customer",
                user: {
                    email: "[email protected]",
                    password: "boo",
                },
            },
            {
                include: [User],
            }
        );
    });

    this.afterEach(async () => {
        sandbox.restore();
    });

    it("should create a new user", function (done) {
        agent
            .post("/api/users/signUp")
            .send({
                firstName: "firstName",
                lastName: "lastName",
                email: "[email protected]",
                password: "aPassword",
            })
            .expect(201)
            .end((err, res) => {
                err ? done(err) : done();
            });
    });

    it("should sign in", function (done) {
        login(agent, { email: "[email protected]", password: "boo" })
            .expect(200)
            .end((err, res) => {
                const { message, user } = res.body;
                message.should.be.equal("valid");
                user.should.have.property("email").equal("[email protected]");
                user.should.have.property("customer").have.property("firstName").equal("Test");
                user.should.have.property("customer").have.property("lastName").equal("Customer");

                agent
                    .get("/api/users/me")
                    .expect(200)
                    .end((err, res) => {
                        const user = res.body;
                        user.should.have.property("email").equal("[email protected]");
                        user.should.have.property("customer").have.property("firstName").equal("Test");
                        user.should.have.property("customer").have.property("lastName").equal("Customer");
                        err ? done(err) : done();
                    });
            });
    });

    it("should sign out", async function () {
        await login(agent, { email: "[email protected]", password: "boo" });
        await logout(agent)
            .expect(302)
            .expect("Location", "/");

        return agent
            .get("/api/users/me")
            .expect(401);
    });

    it("should return unauthorized", function (done) {
        agent
            .get("/api/users/me")
            .expect(401)
            .end((err, res) => {
                err ? done(err) : done();
            });
    });
});

рдореИрдВ рднреА рдЗрд╕ рдореБрджреНрджреЗ рдХреЛ рджреЗрдЦ рд░рд╣рд╛ рд╣реВрдВред рдпрд╣ рдмрдВрдж рдХреНрдпреЛрдВ рд╣реИ?

рдпрд╣ рд╣реЛ рд╕рдХрддрд╛ рд╣реИ рдХрд┐ рдкреБрдирд░реНрд╕реНрдерд╛рдкрдирд╛ рдХрд╛ рд╡реНрдпрд╡рд╣рд╛рд░ рд╕реНрдЯрдм рдХреЛ рд╕реЗрдЯ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдЙрдкрдпреЛрдЧ рдХрд┐рдП рдЬрд╛рдиреЗ рд╡рд╛рд▓реЗ рд╡рд┐рд╢рд┐рд╖реНрдЯ рд╕рд┐рдВрдЯреИрдХреНрд╕ рдХреЗ рдкреНрд░рддрд┐ рд╕рдВрд╡реЗрджрдирд╢реАрд▓ рд╣реЛред рдЕрдлреИрдХ рдпреЗ рджреЛрдиреЛрдВ _рдЪрд╛рд╣рд┐рдП_ рдРрд╕рд╛ рдХрд░рдиреЗ рдХреЗ рд╡реИрдз рддрд░реАрдХреЗ рд╣реИрдВ рд▓реЗрдХрд┐рди рдкреБрдирд░реНрд╕реНрдерд╛рдкрдирд╛() рдЕрд▓рдЧ рд╣реИред рдПрдХ рдореЗрдВ рдпрд╣ рдХрд╛рдо рдХрд░рддрд╛ рд╣реИ рдФрд░ рджреВрд╕рд░реЗ рдореЗрдВ рдЗрд╕рдХреЗ рдЕрдкреНрд░рддреНрдпрд╛рд╢рд┐рдд рдкрд░рд┐рдгрд╛рдо рд╣реЛрддреЗ рд╣реИрдВред

const sinon = require('sinon')

// restore does not work as expected

let db = {
  query: () => {
    return 'my query 1'
  }
}
const dbStub = sinon.stub().resolves('somejson')
db.query = dbStub
db.query() // somejson
sinon.restore()
db.query() // resolves nothing

// restore works as expected

db = {
  query: () => {
    return 'my query 1'
  }
}
sinon.stub(db, 'query').resolves('somejson')
db.query() // somejson
sinon.restore()
db.query() //my query 1

@scottpatrickwright рдЬрдм рдЖрдк рд╕реАрдзреЗ db.query рдЕрд╕рд╛рдЗрдирдореЗрдВрдЯ рдХрд░рддреЗ рд╣реИрдВ, рддреЛ sinon рдХреЛ рдпрд╣ рдЬрд╛рдирдиреЗ рдХрд╛ рдХреЛрдИ рддрд░реАрдХрд╛ рдирд╣реАрдВ рд╣реИ рдХрд┐ рдХреНрдпрд╛ рдкреБрдирд░реНрд╕реНрдерд╛рдкрд┐рдд рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ рдпрд╛ рдЕрд╕рд╛рдЗрдирдореЗрдВрдЯ рд╕реЗ рдкрд╣рд▓реЗ рдореВрд▓реНрдп рдХреНрдпрд╛ рдерд╛ред

@scottpatrickwright рдпрд╣ рдмрдВрдж рд╣реИ рдХреНрдпреЛрдВрдХрд┐ рдХреЛрдИ рдмрдЧ рдирд╣реАрдВ рд╣реИ, рдЬреИрд╕рд╛ рдХрд┐ рдзрд╛рдЧреЗ рд╕реЗ рд╕реНрдкрд╖реНрдЯ рд╣реЛрдирд╛ рдЪрд╛рд╣рд┐рдПред рдпрд╣ рд╕рд┐рд░реНрдл рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рддреНрд░реБрдЯрд┐ рдереА, рдЬреИрд╕рд╛ рдХрд┐ рдЖрдкрдХреЗ рдорд╛рдорд▓реЗ рдореЗрдВ рд╣реИ: рдпрд╣ рд╡рд░реНрдгрд┐рдд рдФрд░ рдЗрдЪреНрдЫрд┐рдд рдХреЗ рд░реВрдк рдореЗрдВ рдХрд╛рдо рдХрд░рддрд╛ рд╣реИ :-)

рд╕рд┐рдиреЙрди рд╕реИрдВрдбрдмреЙрдХреНрд╕ рдЙрди рдЪреАрдЬреЛрдВ рдХреЛ рд╕рд╛рдл рдХрд░ рд╕рдХрддрд╛ рд╣реИ рдЬрд┐рдирдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рд╡рд╣ рдЬрд╛рдирддрд╛ рд╣реИред рдЗрд╕рдХрд╛ рдорддрд▓рдм рд╣реИ рдХрд┐ рд╕рд┐рдиреЙрди рдХреЛ рдЙрди рд╡рд╕реНрддреБрдУрдВ рдХрд╛ рдЬреНрдЮрд╛рди рд╣реЛрдирд╛ рдЪрд╛рд╣рд┐рдП рдЬрд┐рдирдХреЗ рд╕рд╛рде рд╡рд╣ рдмрд╛рддрдЪреАрдд рдХрд░рддрд╛ рд╣реИред рдЖрдкрдХреЗ рджреНрд╡рд╛рд░рд╛ рдКрдкрд░ рд╡рд░реНрдгрд┐рдд рдареВрдВрда рдХреЛ рд╕реНрдЯреИрдВрдбрдЕрд▓реЛрди рдмрдирд╛рдпрд╛ рдЧрдпрд╛ рд╣реИред рд╕рд┐рдиреЙрди рдХреЛ рдЖрдкрдХреЗ рджреНрд╡рд╛рд░рд╛ рдЕрд╕рд╛рдЗрди рдХреА рдЧрдИ рдХрд┐рд╕реА рднреА рд╡рд╕реНрддреБ рд╕реЗ рдЕрд╡рдЧрдд рдирд╣реАрдВ рдХрд░рд╛рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред рд╡рд╣ рджреВрд╕рд░реЗ рдЙрджрд╛рд╣рд░рдг рдореЗрдВ рдмрджрд▓ рдЬрд╛рддрд╛ рд╣реИред рдЖрдк рдЗрд╕реЗ рд╣рд╛рд╕рд┐рд▓ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП sinon.replace(obj, fieldname, fake) рднреА рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред рдпрд╣ рд╕рд┐рд░реНрдл рдЬрд╛рд╡рд╛рд╕реНрдХреНрд░рд┐рдкреНрдЯ рд╣реИ

рдХрд╛рдлреА рдЙрдЪрд┐рдд - рд╕реНрдкрд╖реНрдЯреАрдХрд░рдг рдХреЗ рд▓рд┐рдП рдзрдиреНрдпрд╡рд╛рджред

рд╢реБрдХреНрд░, 19 рдлрд░рд╡рд░реА, 2021 рдХреЛ рд╕реБрдмрд╣ 7:02 рдмрдЬреЗ рдХрд╛рд░реНрд▓-рдПрд░рд┐рдХ рдХреЛрдкреНрд╕реЗрдВрдЧ рдиреЛрдЯрд┐рдлрд┐рдХреЗрд╢рди @github.com
рд▓рд┐рдЦрд╛ рдерд╛:

@scottpatrickwright https://github.com/scottpatrickwright рдпрд╣ рдмрдВрдж рд╣реИ
рдХреНрдпреЛрдВрдХрд┐ рдХреЛрдИ рдмрдЧ рдирд╣реАрдВ рд╣реИ, рдЬреИрд╕рд╛ рдХрд┐ рдзрд╛рдЧреЗ рд╕реЗ рд╕реНрдкрд╖реНрдЯ рд╣реЛрдирд╛ рдЪрд╛рд╣рд┐рдПред рдпрд╣ рдмрд╕ рдЧрдпрд╛ рдерд╛
рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рддреНрд░реБрдЯрд┐, рдЬреИрд╕рд╛ рдХрд┐ рдЖрдкрдХреЗ рдорд╛рдорд▓реЗ рдореЗрдВ рд╣реИ: рдпрд╣ рд╡рд░реНрдгрд┐рдд рдФрд░ рдЗрдЪреНрдЫрд┐рдд рдХреЗ рд░реВрдк рдореЗрдВ рдХрд╛рдо рдХрд░рддрд╛ рд╣реИ :-)

рд╕рд┐рдиреЙрди рд╕реИрдВрдбрдмреЙрдХреНрд╕ рдЙрди рдЪреАрдЬреЛрдВ рдХреЛ рд╕рд╛рдл рдХрд░ рд╕рдХрддрд╛ рд╣реИ рдЬрд┐рдирдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рд╡рд╣ рдЬрд╛рдирддрд╛ рд╣реИред рдЗрд╕рдХрд╛ рдорддрд▓рдм рд╣реИ рдХрд┐ рд╕рд┐рдиреЛрди рд╣реИ
рдЙрди рд╡рд╕реНрддреБрдУрдВ рдХрд╛ рдЬреНрдЮрд╛рди рдкреНрд░рд╛рдкреНрдд рдХрд░рдирд╛ рдЬрд┐рдирдХреЗ рд╕рд╛рде рдпрд╣ рдмрд╛рддрдЪреАрдд рдХрд░рддрд╛ рд╣реИ рдареВрдВрда рдЖрдк
рдКрдкрд░ рд╡рд░реНрдгрд┐рдд рд╕реНрдЯреИрдВрдбрдЕрд▓реЛрди рдмрдирд╛рдпрд╛ рдЧрдпрд╛ рд╣реИред рд╕рд┐рдиреЙрди рдХреЛ рдЗрдирдореЗрдВ рд╕реЗ рдХрд┐рд╕реА рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдЕрд╡рдЧрдд рдирд╣реАрдВ рдХрд░рд╛рдпрд╛ рдЧрдпрд╛ рд╣реИ
рдЬрд┐рди рд╡рд╕реНрддреБрдУрдВ рдХреЛ рдЖрдк рдЗрд╕реЗ рдЕрд╕рд╛рдЗрди рдХрд░рддреЗ рд╣реИрдВред рд╡рд╣ рджреВрд╕рд░реЗ рдЙрджрд╛рд╣рд░рдг рдореЗрдВ рдмрджрд▓ рдЬрд╛рддрд╛ рд╣реИред рдЖрдк рдРрд╕рд╛ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ
рдЗрд╕реЗ рдкреНрд░рд╛рдкреНрдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП sinon.replace(obj, fieldname, fake) рднреА рдХрд░реЗрдВред рдпрд╣ рд╕рд┐рд░реНрдл
рдЬрд╛рд╡рд╛рд╕реНрдХреНрд░рд┐рдкреНрдЯ

-
рдЖрдк рдЗрд╕реЗ рдкреНрд░рд╛рдкреНрдд рдХрд░ рд░рд╣реЗ рд╣реИрдВ рдХреНрдпреЛрдВрдХрд┐ рдЖрдкрдХрд╛ рдЙрд▓реНрд▓реЗрдЦ рдХрд┐рдпрд╛ рдЧрдпрд╛ рдерд╛ред
рдЗрд╕ рдИрдореЗрд▓ рдХрд╛ рд╕реАрдзреЗ рдЙрддреНрддрд░ рджреЗрдВ, рдЗрд╕реЗ GitHub рдкрд░ рджреЗрдЦреЗрдВ
https://github.com/sinonjs/sinon/issues/2065#issuecomment-782032440 , рдпрд╛
рд╕рджрд╕реНрдпрддрд╛ рд╕рдорд╛рдкреНрдд
https://github.com/notifications/unsubscribe-auth/AAKKRTFVMGEYVIB5GSIDXKTS7ZHMRANCNFSM4IGBASQQ
.

рдХреНрдпрд╛ рдпрд╣ рдкреГрд╖реНрда рдЙрдкрдпреЛрдЧреА рдерд╛?
0 / 5 - 0 рд░реЗрдЯрд┐рдВрдЧреНрд╕

рд╕рдВрдмрдВрдзрд┐рдд рдореБрджреНрджреЛрдВ

stephanwlee picture stephanwlee  ┬╖  3рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ

ljian3377 picture ljian3377  ┬╖  3рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ

andys8 picture andys8  ┬╖  4рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ

ALeschinsky picture ALeschinsky  ┬╖  4рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ

zimtsui picture zimtsui  ┬╖  3рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ