chore: sync infra docs and coverage
This commit is contained in:
		
							
								
								
									
										1127
									
								
								backend/coverage/clover.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1127
									
								
								backend/coverage/clover.xml
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										13
									
								
								backend/coverage/coverage-final.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								backend/coverage/coverage-final.json
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										224
									
								
								backend/coverage/lcov-report/base.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										224
									
								
								backend/coverage/lcov-report/base.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,224 @@ | ||||
| body, html { | ||||
|   margin:0; padding: 0; | ||||
|   height: 100%; | ||||
| } | ||||
| body { | ||||
|     font-family: Helvetica Neue, Helvetica, Arial; | ||||
|     font-size: 14px; | ||||
|     color:#333; | ||||
| } | ||||
| .small { font-size: 12px; } | ||||
| *, *:after, *:before { | ||||
|   -webkit-box-sizing:border-box; | ||||
|      -moz-box-sizing:border-box; | ||||
|           box-sizing:border-box; | ||||
|   } | ||||
| h1 { font-size: 20px; margin: 0;} | ||||
| h2 { font-size: 14px; } | ||||
| pre { | ||||
|     font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace; | ||||
|     margin: 0; | ||||
|     padding: 0; | ||||
|     -moz-tab-size: 2; | ||||
|     -o-tab-size:  2; | ||||
|     tab-size: 2; | ||||
| } | ||||
| a { color:#0074D9; text-decoration:none; } | ||||
| a:hover { text-decoration:underline; } | ||||
| .strong { font-weight: bold; } | ||||
| .space-top1 { padding: 10px 0 0 0; } | ||||
| .pad2y { padding: 20px 0; } | ||||
| .pad1y { padding: 10px 0; } | ||||
| .pad2x { padding: 0 20px; } | ||||
| .pad2 { padding: 20px; } | ||||
| .pad1 { padding: 10px; } | ||||
| .space-left2 { padding-left:55px; } | ||||
| .space-right2 { padding-right:20px; } | ||||
| .center { text-align:center; } | ||||
| .clearfix { display:block; } | ||||
| .clearfix:after { | ||||
|   content:''; | ||||
|   display:block; | ||||
|   height:0; | ||||
|   clear:both; | ||||
|   visibility:hidden; | ||||
|   } | ||||
| .fl { float: left; } | ||||
| @media only screen and (max-width:640px) { | ||||
|   .col3 { width:100%; max-width:100%; } | ||||
|   .hide-mobile { display:none!important; } | ||||
| } | ||||
|  | ||||
| .quiet { | ||||
|   color: #7f7f7f; | ||||
|   color: rgba(0,0,0,0.5); | ||||
| } | ||||
| .quiet a { opacity: 0.7; } | ||||
|  | ||||
| .fraction { | ||||
|   font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; | ||||
|   font-size: 10px; | ||||
|   color: #555; | ||||
|   background: #E8E8E8; | ||||
|   padding: 4px 5px; | ||||
|   border-radius: 3px; | ||||
|   vertical-align: middle; | ||||
| } | ||||
|  | ||||
| div.path a:link, div.path a:visited { color: #333; } | ||||
| table.coverage { | ||||
|   border-collapse: collapse; | ||||
|   margin: 10px 0 0 0; | ||||
|   padding: 0; | ||||
| } | ||||
|  | ||||
| table.coverage td { | ||||
|   margin: 0; | ||||
|   padding: 0; | ||||
|   vertical-align: top; | ||||
| } | ||||
| table.coverage td.line-count { | ||||
|     text-align: right; | ||||
|     padding: 0 5px 0 20px; | ||||
| } | ||||
| table.coverage td.line-coverage { | ||||
|     text-align: right; | ||||
|     padding-right: 10px; | ||||
|     min-width:20px; | ||||
| } | ||||
|  | ||||
| table.coverage td span.cline-any { | ||||
|     display: inline-block; | ||||
|     padding: 0 5px; | ||||
|     width: 100%; | ||||
| } | ||||
| .missing-if-branch { | ||||
|     display: inline-block; | ||||
|     margin-right: 5px; | ||||
|     border-radius: 3px; | ||||
|     position: relative; | ||||
|     padding: 0 4px; | ||||
|     background: #333; | ||||
|     color: yellow; | ||||
| } | ||||
|  | ||||
| .skip-if-branch { | ||||
|     display: none; | ||||
|     margin-right: 10px; | ||||
|     position: relative; | ||||
|     padding: 0 4px; | ||||
|     background: #ccc; | ||||
|     color: white; | ||||
| } | ||||
| .missing-if-branch .typ, .skip-if-branch .typ { | ||||
|     color: inherit !important; | ||||
| } | ||||
| .coverage-summary { | ||||
|   border-collapse: collapse; | ||||
|   width: 100%; | ||||
| } | ||||
| .coverage-summary tr { border-bottom: 1px solid #bbb; } | ||||
| .keyline-all { border: 1px solid #ddd; } | ||||
| .coverage-summary td, .coverage-summary th { padding: 10px; } | ||||
| .coverage-summary tbody { border: 1px solid #bbb; } | ||||
| .coverage-summary td { border-right: 1px solid #bbb; } | ||||
| .coverage-summary td:last-child { border-right: none; } | ||||
| .coverage-summary th { | ||||
|   text-align: left; | ||||
|   font-weight: normal; | ||||
|   white-space: nowrap; | ||||
| } | ||||
| .coverage-summary th.file { border-right: none !important; } | ||||
| .coverage-summary th.pct { } | ||||
| .coverage-summary th.pic, | ||||
| .coverage-summary th.abs, | ||||
| .coverage-summary td.pct, | ||||
| .coverage-summary td.abs { text-align: right; } | ||||
| .coverage-summary td.file { white-space: nowrap;  } | ||||
| .coverage-summary td.pic { min-width: 120px !important;  } | ||||
| .coverage-summary tfoot td { } | ||||
|  | ||||
| .coverage-summary .sorter { | ||||
|     height: 10px; | ||||
|     width: 7px; | ||||
|     display: inline-block; | ||||
|     margin-left: 0.5em; | ||||
|     background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent; | ||||
| } | ||||
| .coverage-summary .sorted .sorter { | ||||
|     background-position: 0 -20px; | ||||
| } | ||||
| .coverage-summary .sorted-desc .sorter { | ||||
|     background-position: 0 -10px; | ||||
| } | ||||
| .status-line {  height: 10px; } | ||||
| /* yellow */ | ||||
| .cbranch-no { background: yellow !important; color: #111; } | ||||
| /* dark red */ | ||||
| .red.solid, .status-line.low, .low .cover-fill { background:#C21F39 } | ||||
| .low .chart { border:1px solid #C21F39 } | ||||
| .highlighted, | ||||
| .highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{ | ||||
|   background: #C21F39 !important; | ||||
| } | ||||
| /* medium red */ | ||||
| .cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE } | ||||
| /* light red */ | ||||
| .low, .cline-no { background:#FCE1E5 } | ||||
| /* light green */ | ||||
| .high, .cline-yes { background:rgb(230,245,208) } | ||||
| /* medium green */ | ||||
| .cstat-yes { background:rgb(161,215,106) } | ||||
| /* dark green */ | ||||
| .status-line.high, .high .cover-fill { background:rgb(77,146,33) } | ||||
| .high .chart { border:1px solid rgb(77,146,33) } | ||||
| /* dark yellow (gold) */ | ||||
| .status-line.medium, .medium .cover-fill { background: #f9cd0b; } | ||||
| .medium .chart { border:1px solid #f9cd0b; } | ||||
| /* light yellow */ | ||||
| .medium { background: #fff4c2; } | ||||
|  | ||||
| .cstat-skip { background: #ddd; color: #111; } | ||||
| .fstat-skip { background: #ddd; color: #111 !important; } | ||||
| .cbranch-skip { background: #ddd !important; color: #111; } | ||||
|  | ||||
| span.cline-neutral { background: #eaeaea; } | ||||
|  | ||||
| .coverage-summary td.empty { | ||||
|     opacity: .5; | ||||
|     padding-top: 4px; | ||||
|     padding-bottom: 4px; | ||||
|     line-height: 1; | ||||
|     color: #888; | ||||
| } | ||||
|  | ||||
| .cover-fill, .cover-empty { | ||||
|   display:inline-block; | ||||
|   height: 12px; | ||||
| } | ||||
| .chart { | ||||
|   line-height: 0; | ||||
| } | ||||
| .cover-empty { | ||||
|     background: white; | ||||
| } | ||||
| .cover-full { | ||||
|     border-right: none !important; | ||||
| } | ||||
| pre.prettyprint { | ||||
|     border: none !important; | ||||
|     padding: 0 !important; | ||||
|     margin: 0 !important; | ||||
| } | ||||
| .com { color: #999 !important; } | ||||
| .ignore-none { color: #999; font-weight: normal; } | ||||
|  | ||||
| .wrapper { | ||||
|   min-height: 100%; | ||||
|   height: auto !important; | ||||
|   height: 100%; | ||||
|   margin: 0 auto -48px; | ||||
| } | ||||
| .footer, .push { | ||||
|   height: 48px; | ||||
| } | ||||
							
								
								
									
										87
									
								
								backend/coverage/lcov-report/block-navigation.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								backend/coverage/lcov-report/block-navigation.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,87 @@ | ||||
| /* eslint-disable */ | ||||
| var jumpToCode = (function init() { | ||||
|     // Classes of code we would like to highlight in the file view | ||||
|     var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no']; | ||||
|  | ||||
|     // Elements to highlight in the file listing view | ||||
|     var fileListingElements = ['td.pct.low']; | ||||
|  | ||||
|     // We don't want to select elements that are direct descendants of another match | ||||
|     var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > ` | ||||
|  | ||||
|     // Selector that finds elements on the page to which we can jump | ||||
|     var selector = | ||||
|         fileListingElements.join(', ') + | ||||
|         ', ' + | ||||
|         notSelector + | ||||
|         missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b` | ||||
|  | ||||
|     // The NodeList of matching elements | ||||
|     var missingCoverageElements = document.querySelectorAll(selector); | ||||
|  | ||||
|     var currentIndex; | ||||
|  | ||||
|     function toggleClass(index) { | ||||
|         missingCoverageElements | ||||
|             .item(currentIndex) | ||||
|             .classList.remove('highlighted'); | ||||
|         missingCoverageElements.item(index).classList.add('highlighted'); | ||||
|     } | ||||
|  | ||||
|     function makeCurrent(index) { | ||||
|         toggleClass(index); | ||||
|         currentIndex = index; | ||||
|         missingCoverageElements.item(index).scrollIntoView({ | ||||
|             behavior: 'smooth', | ||||
|             block: 'center', | ||||
|             inline: 'center' | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     function goToPrevious() { | ||||
|         var nextIndex = 0; | ||||
|         if (typeof currentIndex !== 'number' || currentIndex === 0) { | ||||
|             nextIndex = missingCoverageElements.length - 1; | ||||
|         } else if (missingCoverageElements.length > 1) { | ||||
|             nextIndex = currentIndex - 1; | ||||
|         } | ||||
|  | ||||
|         makeCurrent(nextIndex); | ||||
|     } | ||||
|  | ||||
|     function goToNext() { | ||||
|         var nextIndex = 0; | ||||
|  | ||||
|         if ( | ||||
|             typeof currentIndex === 'number' && | ||||
|             currentIndex < missingCoverageElements.length - 1 | ||||
|         ) { | ||||
|             nextIndex = currentIndex + 1; | ||||
|         } | ||||
|  | ||||
|         makeCurrent(nextIndex); | ||||
|     } | ||||
|  | ||||
|     return function jump(event) { | ||||
|         if ( | ||||
|             document.getElementById('fileSearch') === document.activeElement && | ||||
|             document.activeElement != null | ||||
|         ) { | ||||
|             // if we're currently focused on the search input, we don't want to navigate | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         switch (event.which) { | ||||
|             case 78: // n | ||||
|             case 74: // j | ||||
|                 goToNext(); | ||||
|                 break; | ||||
|             case 66: // b | ||||
|             case 75: // k | ||||
|             case 80: // p | ||||
|                 goToPrevious(); | ||||
|                 break; | ||||
|         } | ||||
|     }; | ||||
| })(); | ||||
| window.addEventListener('keydown', jumpToCode); | ||||
							
								
								
									
										
											BIN
										
									
								
								backend/coverage/lcov-report/favicon.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								backend/coverage/lcov-report/favicon.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 445 B | 
							
								
								
									
										191
									
								
								backend/coverage/lcov-report/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										191
									
								
								backend/coverage/lcov-report/index.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,191 @@ | ||||
|  | ||||
| <!doctype html> | ||||
| <html lang="en"> | ||||
|  | ||||
| <head> | ||||
|     <title>Code coverage report for All files</title> | ||||
|     <meta charset="utf-8" /> | ||||
|     <link rel="stylesheet" href="prettify.css" /> | ||||
|     <link rel="stylesheet" href="base.css" /> | ||||
|     <link rel="shortcut icon" type="image/x-icon" href="favicon.png" /> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1" /> | ||||
|     <style type='text/css'> | ||||
|         .coverage-summary .sorter { | ||||
|             background-image: url(sort-arrow-sprite.png); | ||||
|         } | ||||
|     </style> | ||||
| </head> | ||||
|      | ||||
| <body> | ||||
| <div class='wrapper'> | ||||
|     <div class='pad1'> | ||||
|         <h1>All files</h1> | ||||
|         <div class='clearfix'> | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">61.6% </span> | ||||
|                 <span class="quiet">Statements</span> | ||||
|                 <span class='fraction'>661/1073</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">48.83% </span> | ||||
|                 <span class="quiet">Branches</span> | ||||
|                 <span class='fraction'>231/473</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">78.33% </span> | ||||
|                 <span class="quiet">Functions</span> | ||||
|                 <span class='fraction'>47/60</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">61.57% </span> | ||||
|                 <span class="quiet">Lines</span> | ||||
|                 <span class='fraction'>657/1067</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|         </div> | ||||
|         <p class="quiet"> | ||||
|             Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block. | ||||
|         </p> | ||||
|         <template id="filterTemplate"> | ||||
|             <div class="quiet"> | ||||
|                 Filter: | ||||
|                 <input type="search" id="fileSearch"> | ||||
|             </div> | ||||
|         </template> | ||||
|     </div> | ||||
|     <div class='status-line high'></div> | ||||
|     <div class="pad1"> | ||||
| <table class="coverage-summary"> | ||||
| <thead> | ||||
| <tr> | ||||
|    <th data-col="file" data-fmt="html" data-html="true" class="file">File</th> | ||||
|    <th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th> | ||||
|    <th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th> | ||||
|    <th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th> | ||||
|    <th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th> | ||||
|    <th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th> | ||||
|    <th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th> | ||||
|    <th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th> | ||||
|    <th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th> | ||||
|    <th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th> | ||||
| </tr> | ||||
| </thead> | ||||
| <tbody><tr> | ||||
| 	<td class="file high" data-value="src"><a href="src/index.html">src</a></td> | ||||
| 	<td data-value="86.84" class="pic high"> | ||||
| 	<div class="chart"><div class="cover-fill" style="width: 86%"></div><div class="cover-empty" style="width: 14%"></div></div> | ||||
| 	</td> | ||||
| 	<td data-value="86.84" class="pct high">86.84%</td> | ||||
| 	<td data-value="38" class="abs high">33/38</td> | ||||
| 	<td data-value="37.5" class="pct low">37.5%</td> | ||||
| 	<td data-value="8" class="abs low">3/8</td> | ||||
| 	<td data-value="0" class="pct low">0%</td> | ||||
| 	<td data-value="4" class="abs low">0/4</td> | ||||
| 	<td data-value="86.84" class="pct high">86.84%</td> | ||||
| 	<td data-value="38" class="abs high">33/38</td> | ||||
| 	</tr> | ||||
|  | ||||
| <tr> | ||||
| 	<td class="file high" data-value="src/config"><a href="src/config/index.html">src/config</a></td> | ||||
| 	<td data-value="84.21" class="pic high"> | ||||
| 	<div class="chart"><div class="cover-fill" style="width: 84%"></div><div class="cover-empty" style="width: 16%"></div></div> | ||||
| 	</td> | ||||
| 	<td data-value="84.21" class="pct high">84.21%</td> | ||||
| 	<td data-value="19" class="abs high">16/19</td> | ||||
| 	<td data-value="64" class="pct high">64%</td> | ||||
| 	<td data-value="25" class="abs high">16/25</td> | ||||
| 	<td data-value="100" class="pct high">100%</td> | ||||
| 	<td data-value="3" class="abs high">3/3</td> | ||||
| 	<td data-value="83.33" class="pct high">83.33%</td> | ||||
| 	<td data-value="18" class="abs high">15/18</td> | ||||
| 	</tr> | ||||
|  | ||||
| <tr> | ||||
| 	<td class="file high" data-value="src/database"><a href="src/database/index.html">src/database</a></td> | ||||
| 	<td data-value="72.72" class="pic high"> | ||||
| 	<div class="chart"><div class="cover-fill" style="width: 72%"></div><div class="cover-empty" style="width: 28%"></div></div> | ||||
| 	</td> | ||||
| 	<td data-value="72.72" class="pct high">72.72%</td> | ||||
| 	<td data-value="11" class="abs high">8/11</td> | ||||
| 	<td data-value="25" class="pct low">25%</td> | ||||
| 	<td data-value="8" class="abs low">2/8</td> | ||||
| 	<td data-value="50" class="pct medium">50%</td> | ||||
| 	<td data-value="2" class="abs medium">1/2</td> | ||||
| 	<td data-value="72.72" class="pct high">72.72%</td> | ||||
| 	<td data-value="11" class="abs high">8/11</td> | ||||
| 	</tr> | ||||
|  | ||||
| <tr> | ||||
| 	<td class="file high" data-value="src/middleware"><a href="src/middleware/index.html">src/middleware</a></td> | ||||
| 	<td data-value="85.18" class="pic high"> | ||||
| 	<div class="chart"><div class="cover-fill" style="width: 85%"></div><div class="cover-empty" style="width: 15%"></div></div> | ||||
| 	</td> | ||||
| 	<td data-value="85.18" class="pct high">85.18%</td> | ||||
| 	<td data-value="27" class="abs high">23/27</td> | ||||
| 	<td data-value="66.66" class="pct high">66.66%</td> | ||||
| 	<td data-value="12" class="abs high">8/12</td> | ||||
| 	<td data-value="100" class="pct high">100%</td> | ||||
| 	<td data-value="3" class="abs high">3/3</td> | ||||
| 	<td data-value="85.18" class="pct high">85.18%</td> | ||||
| 	<td data-value="27" class="abs high">23/27</td> | ||||
| 	</tr> | ||||
|  | ||||
| <tr> | ||||
| 	<td class="file medium" data-value="src/routes"><a href="src/routes/index.html">src/routes</a></td> | ||||
| 	<td data-value="58.38" class="pic medium"> | ||||
| 	<div class="chart"><div class="cover-fill" style="width: 58%"></div><div class="cover-empty" style="width: 42%"></div></div> | ||||
| 	</td> | ||||
| 	<td data-value="58.38" class="pct medium">58.38%</td> | ||||
| 	<td data-value="942" class="abs medium">550/942</td> | ||||
| 	<td data-value="45.12" class="pct low">45.12%</td> | ||||
| 	<td data-value="390" class="abs low">176/390</td> | ||||
| 	<td data-value="80.95" class="pct high">80.95%</td> | ||||
| 	<td data-value="42" class="abs high">34/42</td> | ||||
| 	<td data-value="58.42" class="pct medium">58.42%</td> | ||||
| 	<td data-value="938" class="abs medium">548/938</td> | ||||
| 	</tr> | ||||
|  | ||||
| <tr> | ||||
| 	<td class="file high" data-value="src/tests"><a href="src/tests/index.html">src/tests</a></td> | ||||
| 	<td data-value="86.11" class="pic high"> | ||||
| 	<div class="chart"><div class="cover-fill" style="width: 86%"></div><div class="cover-empty" style="width: 14%"></div></div> | ||||
| 	</td> | ||||
| 	<td data-value="86.11" class="pct high">86.11%</td> | ||||
| 	<td data-value="36" class="abs high">31/36</td> | ||||
| 	<td data-value="86.66" class="pct high">86.66%</td> | ||||
| 	<td data-value="30" class="abs high">26/30</td> | ||||
| 	<td data-value="100" class="pct high">100%</td> | ||||
| 	<td data-value="6" class="abs high">6/6</td> | ||||
| 	<td data-value="85.71" class="pct high">85.71%</td> | ||||
| 	<td data-value="35" class="abs high">30/35</td> | ||||
| 	</tr> | ||||
|  | ||||
| </tbody> | ||||
| </table> | ||||
| </div> | ||||
|                 <div class='push'></div><!-- for sticky footer --> | ||||
|             </div><!-- /wrapper --> | ||||
|             <div class='footer quiet pad2 space-top1 center small'> | ||||
|                 Code coverage generated by | ||||
|                 <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> | ||||
|                 at 2025-10-17T03:41:07.369Z | ||||
|             </div> | ||||
|         <script src="prettify.js"></script> | ||||
|         <script> | ||||
|             window.onload = function () { | ||||
|                 prettyPrint(); | ||||
|             }; | ||||
|         </script> | ||||
|         <script src="sorter.js"></script> | ||||
|         <script src="block-navigation.js"></script> | ||||
|     </body> | ||||
| </html> | ||||
|      | ||||
							
								
								
									
										1
									
								
								backend/coverage/lcov-report/prettify.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								backend/coverage/lcov-report/prettify.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| .pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} | ||||
							
								
								
									
										2
									
								
								backend/coverage/lcov-report/prettify.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								backend/coverage/lcov-report/prettify.js
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								backend/coverage/lcov-report/sort-arrow-sprite.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								backend/coverage/lcov-report/sort-arrow-sprite.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 138 B | 
							
								
								
									
										210
									
								
								backend/coverage/lcov-report/sorter.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										210
									
								
								backend/coverage/lcov-report/sorter.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,210 @@ | ||||
| /* eslint-disable */ | ||||
| var addSorting = (function() { | ||||
|     'use strict'; | ||||
|     var cols, | ||||
|         currentSort = { | ||||
|             index: 0, | ||||
|             desc: false | ||||
|         }; | ||||
|  | ||||
|     // returns the summary table element | ||||
|     function getTable() { | ||||
|         return document.querySelector('.coverage-summary'); | ||||
|     } | ||||
|     // returns the thead element of the summary table | ||||
|     function getTableHeader() { | ||||
|         return getTable().querySelector('thead tr'); | ||||
|     } | ||||
|     // returns the tbody element of the summary table | ||||
|     function getTableBody() { | ||||
|         return getTable().querySelector('tbody'); | ||||
|     } | ||||
|     // returns the th element for nth column | ||||
|     function getNthColumn(n) { | ||||
|         return getTableHeader().querySelectorAll('th')[n]; | ||||
|     } | ||||
|  | ||||
|     function onFilterInput() { | ||||
|         const searchValue = document.getElementById('fileSearch').value; | ||||
|         const rows = document.getElementsByTagName('tbody')[0].children; | ||||
|  | ||||
|         // Try to create a RegExp from the searchValue. If it fails (invalid regex), | ||||
|         // it will be treated as a plain text search | ||||
|         let searchRegex; | ||||
|         try { | ||||
|             searchRegex = new RegExp(searchValue, 'i'); // 'i' for case-insensitive | ||||
|         } catch (error) { | ||||
|             searchRegex = null; | ||||
|         } | ||||
|  | ||||
|         for (let i = 0; i < rows.length; i++) { | ||||
|             const row = rows[i]; | ||||
|             let isMatch = false; | ||||
|  | ||||
|             if (searchRegex) { | ||||
|                 // If a valid regex was created, use it for matching | ||||
|                 isMatch = searchRegex.test(row.textContent); | ||||
|             } else { | ||||
|                 // Otherwise, fall back to the original plain text search | ||||
|                 isMatch = row.textContent | ||||
|                     .toLowerCase() | ||||
|                     .includes(searchValue.toLowerCase()); | ||||
|             } | ||||
|  | ||||
|             row.style.display = isMatch ? '' : 'none'; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // loads the search box | ||||
|     function addSearchBox() { | ||||
|         var template = document.getElementById('filterTemplate'); | ||||
|         var templateClone = template.content.cloneNode(true); | ||||
|         templateClone.getElementById('fileSearch').oninput = onFilterInput; | ||||
|         template.parentElement.appendChild(templateClone); | ||||
|     } | ||||
|  | ||||
|     // loads all columns | ||||
|     function loadColumns() { | ||||
|         var colNodes = getTableHeader().querySelectorAll('th'), | ||||
|             colNode, | ||||
|             cols = [], | ||||
|             col, | ||||
|             i; | ||||
|  | ||||
|         for (i = 0; i < colNodes.length; i += 1) { | ||||
|             colNode = colNodes[i]; | ||||
|             col = { | ||||
|                 key: colNode.getAttribute('data-col'), | ||||
|                 sortable: !colNode.getAttribute('data-nosort'), | ||||
|                 type: colNode.getAttribute('data-type') || 'string' | ||||
|             }; | ||||
|             cols.push(col); | ||||
|             if (col.sortable) { | ||||
|                 col.defaultDescSort = col.type === 'number'; | ||||
|                 colNode.innerHTML = | ||||
|                     colNode.innerHTML + '<span class="sorter"></span>'; | ||||
|             } | ||||
|         } | ||||
|         return cols; | ||||
|     } | ||||
|     // attaches a data attribute to every tr element with an object | ||||
|     // of data values keyed by column name | ||||
|     function loadRowData(tableRow) { | ||||
|         var tableCols = tableRow.querySelectorAll('td'), | ||||
|             colNode, | ||||
|             col, | ||||
|             data = {}, | ||||
|             i, | ||||
|             val; | ||||
|         for (i = 0; i < tableCols.length; i += 1) { | ||||
|             colNode = tableCols[i]; | ||||
|             col = cols[i]; | ||||
|             val = colNode.getAttribute('data-value'); | ||||
|             if (col.type === 'number') { | ||||
|                 val = Number(val); | ||||
|             } | ||||
|             data[col.key] = val; | ||||
|         } | ||||
|         return data; | ||||
|     } | ||||
|     // loads all row data | ||||
|     function loadData() { | ||||
|         var rows = getTableBody().querySelectorAll('tr'), | ||||
|             i; | ||||
|  | ||||
|         for (i = 0; i < rows.length; i += 1) { | ||||
|             rows[i].data = loadRowData(rows[i]); | ||||
|         } | ||||
|     } | ||||
|     // sorts the table using the data for the ith column | ||||
|     function sortByIndex(index, desc) { | ||||
|         var key = cols[index].key, | ||||
|             sorter = function(a, b) { | ||||
|                 a = a.data[key]; | ||||
|                 b = b.data[key]; | ||||
|                 return a < b ? -1 : a > b ? 1 : 0; | ||||
|             }, | ||||
|             finalSorter = sorter, | ||||
|             tableBody = document.querySelector('.coverage-summary tbody'), | ||||
|             rowNodes = tableBody.querySelectorAll('tr'), | ||||
|             rows = [], | ||||
|             i; | ||||
|  | ||||
|         if (desc) { | ||||
|             finalSorter = function(a, b) { | ||||
|                 return -1 * sorter(a, b); | ||||
|             }; | ||||
|         } | ||||
|  | ||||
|         for (i = 0; i < rowNodes.length; i += 1) { | ||||
|             rows.push(rowNodes[i]); | ||||
|             tableBody.removeChild(rowNodes[i]); | ||||
|         } | ||||
|  | ||||
|         rows.sort(finalSorter); | ||||
|  | ||||
|         for (i = 0; i < rows.length; i += 1) { | ||||
|             tableBody.appendChild(rows[i]); | ||||
|         } | ||||
|     } | ||||
|     // removes sort indicators for current column being sorted | ||||
|     function removeSortIndicators() { | ||||
|         var col = getNthColumn(currentSort.index), | ||||
|             cls = col.className; | ||||
|  | ||||
|         cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, ''); | ||||
|         col.className = cls; | ||||
|     } | ||||
|     // adds sort indicators for current column being sorted | ||||
|     function addSortIndicators() { | ||||
|         getNthColumn(currentSort.index).className += currentSort.desc | ||||
|             ? ' sorted-desc' | ||||
|             : ' sorted'; | ||||
|     } | ||||
|     // adds event listeners for all sorter widgets | ||||
|     function enableUI() { | ||||
|         var i, | ||||
|             el, | ||||
|             ithSorter = function ithSorter(i) { | ||||
|                 var col = cols[i]; | ||||
|  | ||||
|                 return function() { | ||||
|                     var desc = col.defaultDescSort; | ||||
|  | ||||
|                     if (currentSort.index === i) { | ||||
|                         desc = !currentSort.desc; | ||||
|                     } | ||||
|                     sortByIndex(i, desc); | ||||
|                     removeSortIndicators(); | ||||
|                     currentSort.index = i; | ||||
|                     currentSort.desc = desc; | ||||
|                     addSortIndicators(); | ||||
|                 }; | ||||
|             }; | ||||
|         for (i = 0; i < cols.length; i += 1) { | ||||
|             if (cols[i].sortable) { | ||||
|                 // add the click event handler on the th so users | ||||
|                 // dont have to click on those tiny arrows | ||||
|                 el = getNthColumn(i).querySelector('.sorter').parentElement; | ||||
|                 if (el.addEventListener) { | ||||
|                     el.addEventListener('click', ithSorter(i)); | ||||
|                 } else { | ||||
|                     el.attachEvent('onclick', ithSorter(i)); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     // adds sorting functionality to the UI | ||||
|     return function() { | ||||
|         if (!getTable()) { | ||||
|             return; | ||||
|         } | ||||
|         cols = loadColumns(); | ||||
|         loadData(); | ||||
|         addSearchBox(); | ||||
|         addSortIndicators(); | ||||
|         enableUI(); | ||||
|     }; | ||||
| })(); | ||||
|  | ||||
| window.addEventListener('load', addSorting); | ||||
							
								
								
									
										116
									
								
								backend/coverage/lcov-report/src/config/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								backend/coverage/lcov-report/src/config/index.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,116 @@ | ||||
|  | ||||
| <!doctype html> | ||||
| <html lang="en"> | ||||
|  | ||||
| <head> | ||||
|     <title>Code coverage report for src/config</title> | ||||
|     <meta charset="utf-8" /> | ||||
|     <link rel="stylesheet" href="../../prettify.css" /> | ||||
|     <link rel="stylesheet" href="../../base.css" /> | ||||
|     <link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" /> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1" /> | ||||
|     <style type='text/css'> | ||||
|         .coverage-summary .sorter { | ||||
|             background-image: url(../../sort-arrow-sprite.png); | ||||
|         } | ||||
|     </style> | ||||
| </head> | ||||
|      | ||||
| <body> | ||||
| <div class='wrapper'> | ||||
|     <div class='pad1'> | ||||
|         <h1><a href="../../index.html">All files</a> src/config</h1> | ||||
|         <div class='clearfix'> | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">84.21% </span> | ||||
|                 <span class="quiet">Statements</span> | ||||
|                 <span class='fraction'>16/19</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">64% </span> | ||||
|                 <span class="quiet">Branches</span> | ||||
|                 <span class='fraction'>16/25</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">100% </span> | ||||
|                 <span class="quiet">Functions</span> | ||||
|                 <span class='fraction'>3/3</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">83.33% </span> | ||||
|                 <span class="quiet">Lines</span> | ||||
|                 <span class='fraction'>15/18</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|         </div> | ||||
|         <p class="quiet"> | ||||
|             Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block. | ||||
|         </p> | ||||
|         <template id="filterTemplate"> | ||||
|             <div class="quiet"> | ||||
|                 Filter: | ||||
|                 <input type="search" id="fileSearch"> | ||||
|             </div> | ||||
|         </template> | ||||
|     </div> | ||||
|     <div class='status-line high'></div> | ||||
|     <div class="pad1"> | ||||
| <table class="coverage-summary"> | ||||
| <thead> | ||||
| <tr> | ||||
|    <th data-col="file" data-fmt="html" data-html="true" class="file">File</th> | ||||
|    <th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th> | ||||
|    <th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th> | ||||
|    <th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th> | ||||
|    <th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th> | ||||
|    <th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th> | ||||
|    <th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th> | ||||
|    <th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th> | ||||
|    <th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th> | ||||
|    <th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th> | ||||
| </tr> | ||||
| </thead> | ||||
| <tbody><tr> | ||||
| 	<td class="file high" data-value="index.js"><a href="index.js.html">index.js</a></td> | ||||
| 	<td data-value="84.21" class="pic high"> | ||||
| 	<div class="chart"><div class="cover-fill" style="width: 84%"></div><div class="cover-empty" style="width: 16%"></div></div> | ||||
| 	</td> | ||||
| 	<td data-value="84.21" class="pct high">84.21%</td> | ||||
| 	<td data-value="19" class="abs high">16/19</td> | ||||
| 	<td data-value="64" class="pct high">64%</td> | ||||
| 	<td data-value="25" class="abs high">16/25</td> | ||||
| 	<td data-value="100" class="pct high">100%</td> | ||||
| 	<td data-value="3" class="abs high">3/3</td> | ||||
| 	<td data-value="83.33" class="pct high">83.33%</td> | ||||
| 	<td data-value="18" class="abs high">15/18</td> | ||||
| 	</tr> | ||||
|  | ||||
| </tbody> | ||||
| </table> | ||||
| </div> | ||||
|                 <div class='push'></div><!-- for sticky footer --> | ||||
|             </div><!-- /wrapper --> | ||||
|             <div class='footer quiet pad2 space-top1 center small'> | ||||
|                 Code coverage generated by | ||||
|                 <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> | ||||
|                 at 2025-10-17T03:41:07.369Z | ||||
|             </div> | ||||
|         <script src="../../prettify.js"></script> | ||||
|         <script> | ||||
|             window.onload = function () { | ||||
|                 prettyPrint(); | ||||
|             }; | ||||
|         </script> | ||||
|         <script src="../../sorter.js"></script> | ||||
|         <script src="../../block-navigation.js"></script> | ||||
|     </body> | ||||
| </html> | ||||
|      | ||||
							
								
								
									
										241
									
								
								backend/coverage/lcov-report/src/config/index.js.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										241
									
								
								backend/coverage/lcov-report/src/config/index.js.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,241 @@ | ||||
|  | ||||
| <!doctype html> | ||||
| <html lang="en"> | ||||
|  | ||||
| <head> | ||||
|     <title>Code coverage report for src/config/index.js</title> | ||||
|     <meta charset="utf-8" /> | ||||
|     <link rel="stylesheet" href="../../prettify.css" /> | ||||
|     <link rel="stylesheet" href="../../base.css" /> | ||||
|     <link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" /> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1" /> | ||||
|     <style type='text/css'> | ||||
|         .coverage-summary .sorter { | ||||
|             background-image: url(../../sort-arrow-sprite.png); | ||||
|         } | ||||
|     </style> | ||||
| </head> | ||||
|      | ||||
| <body> | ||||
| <div class='wrapper'> | ||||
|     <div class='pad1'> | ||||
|         <h1><a href="../../index.html">All files</a> / <a href="index.html">src/config</a> index.js</h1> | ||||
|         <div class='clearfix'> | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">84.21% </span> | ||||
|                 <span class="quiet">Statements</span> | ||||
|                 <span class='fraction'>16/19</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">64% </span> | ||||
|                 <span class="quiet">Branches</span> | ||||
|                 <span class='fraction'>16/25</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">100% </span> | ||||
|                 <span class="quiet">Functions</span> | ||||
|                 <span class='fraction'>3/3</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">83.33% </span> | ||||
|                 <span class="quiet">Lines</span> | ||||
|                 <span class='fraction'>15/18</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|         </div> | ||||
|         <p class="quiet"> | ||||
|             Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block. | ||||
|         </p> | ||||
|         <template id="filterTemplate"> | ||||
|             <div class="quiet"> | ||||
|                 Filter: | ||||
|                 <input type="search" id="fileSearch"> | ||||
|             </div> | ||||
|         </template> | ||||
|     </div> | ||||
|     <div class='status-line high'></div> | ||||
|     <pre><table class="coverage"> | ||||
| <tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a> | ||||
| <a name='L2'></a><a href='#L2'>2</a> | ||||
| <a name='L3'></a><a href='#L3'>3</a> | ||||
| <a name='L4'></a><a href='#L4'>4</a> | ||||
| <a name='L5'></a><a href='#L5'>5</a> | ||||
| <a name='L6'></a><a href='#L6'>6</a> | ||||
| <a name='L7'></a><a href='#L7'>7</a> | ||||
| <a name='L8'></a><a href='#L8'>8</a> | ||||
| <a name='L9'></a><a href='#L9'>9</a> | ||||
| <a name='L10'></a><a href='#L10'>10</a> | ||||
| <a name='L11'></a><a href='#L11'>11</a> | ||||
| <a name='L12'></a><a href='#L12'>12</a> | ||||
| <a name='L13'></a><a href='#L13'>13</a> | ||||
| <a name='L14'></a><a href='#L14'>14</a> | ||||
| <a name='L15'></a><a href='#L15'>15</a> | ||||
| <a name='L16'></a><a href='#L16'>16</a> | ||||
| <a name='L17'></a><a href='#L17'>17</a> | ||||
| <a name='L18'></a><a href='#L18'>18</a> | ||||
| <a name='L19'></a><a href='#L19'>19</a> | ||||
| <a name='L20'></a><a href='#L20'>20</a> | ||||
| <a name='L21'></a><a href='#L21'>21</a> | ||||
| <a name='L22'></a><a href='#L22'>22</a> | ||||
| <a name='L23'></a><a href='#L23'>23</a> | ||||
| <a name='L24'></a><a href='#L24'>24</a> | ||||
| <a name='L25'></a><a href='#L25'>25</a> | ||||
| <a name='L26'></a><a href='#L26'>26</a> | ||||
| <a name='L27'></a><a href='#L27'>27</a> | ||||
| <a name='L28'></a><a href='#L28'>28</a> | ||||
| <a name='L29'></a><a href='#L29'>29</a> | ||||
| <a name='L30'></a><a href='#L30'>30</a> | ||||
| <a name='L31'></a><a href='#L31'>31</a> | ||||
| <a name='L32'></a><a href='#L32'>32</a> | ||||
| <a name='L33'></a><a href='#L33'>33</a> | ||||
| <a name='L34'></a><a href='#L34'>34</a> | ||||
| <a name='L35'></a><a href='#L35'>35</a> | ||||
| <a name='L36'></a><a href='#L36'>36</a> | ||||
| <a name='L37'></a><a href='#L37'>37</a> | ||||
| <a name='L38'></a><a href='#L38'>38</a> | ||||
| <a name='L39'></a><a href='#L39'>39</a> | ||||
| <a name='L40'></a><a href='#L40'>40</a> | ||||
| <a name='L41'></a><a href='#L41'>41</a> | ||||
| <a name='L42'></a><a href='#L42'>42</a> | ||||
| <a name='L43'></a><a href='#L43'>43</a> | ||||
| <a name='L44'></a><a href='#L44'>44</a> | ||||
| <a name='L45'></a><a href='#L45'>45</a> | ||||
| <a name='L46'></a><a href='#L46'>46</a> | ||||
| <a name='L47'></a><a href='#L47'>47</a> | ||||
| <a name='L48'></a><a href='#L48'>48</a> | ||||
| <a name='L49'></a><a href='#L49'>49</a> | ||||
| <a name='L50'></a><a href='#L50'>50</a> | ||||
| <a name='L51'></a><a href='#L51'>51</a> | ||||
| <a name='L52'></a><a href='#L52'>52</a> | ||||
| <a name='L53'></a><a href='#L53'>53</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">21x</span> | ||||
| <span class="cline-any cline-yes">21x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">14x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">const dotenv = require('dotenv'); | ||||
|   | ||||
| dotenv.config(); | ||||
|   | ||||
| const optionalNumber = (value, fallback) => { | ||||
|   const parsed = parseInt(value, 10); | ||||
|   return Number.isFinite(parsed) ? <span class="branch-0 cbranch-no" title="branch not covered" >parsed </span>: fallback; | ||||
| }; | ||||
|   | ||||
| const deriveDatabaseUrl = () => { | ||||
|   <span class="missing-if-branch" title="if path not taken" >I</span>if (process.env.DATABASE_URL) { | ||||
| <span class="cstat-no" title="statement not covered" >    return process.env.DATABASE_URL;</span> | ||||
|   } | ||||
|   | ||||
|   const { | ||||
|     POSTGRES_USER = <span class="branch-0 cbranch-no" title="branch not covered" >'merchantsofhope_user',</span> | ||||
|     POSTGRES_PASSWORD, | ||||
|     POSTGRES_DB = <span class="branch-0 cbranch-no" title="branch not covered" >'merchantsofhope_supplyanddemandportal',</span> | ||||
|     POSTGRES_HOST = <span class="branch-0 cbranch-no" title="branch not covered" >'merchantsofhope-supplyanddemandportal-database',</span> | ||||
|     POSTGRES_PORT = <span class="branch-0 cbranch-no" title="branch not covered" >'5432'</span> | ||||
|   } = process.env; | ||||
|   | ||||
|   <span class="missing-if-branch" title="if path not taken" >I</span>if (!POSTGRES_PASSWORD) { | ||||
| <span class="cstat-no" title="statement not covered" >    return undefined;</span> | ||||
|   } | ||||
|   | ||||
|   return `postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}`; | ||||
| }; | ||||
|   | ||||
| const config = { | ||||
|   env: process.env.NODE_ENV || <span class="branch-1 cbranch-no" title="branch not covered" >'development',</span> | ||||
|   host: process.env.HOST || process.env.BACKEND_HOST || '0.0.0.0', | ||||
|   port: optionalNumber(process.env.PORT || process.env.BACKEND_PORT, 3001), | ||||
|   databaseUrl: deriveDatabaseUrl(), | ||||
|   jwtSecret: process.env.JWT_SECRET, | ||||
|   corsOrigin: process.env.CORS_ORIGIN || '*', | ||||
|   logLevel: process.env.LOG_LEVEL || 'info', | ||||
|   uploadDir: process.env.UPLOAD_DIR || 'uploads/resumes', | ||||
|   rateLimit: { | ||||
|     windowMs: optionalNumber(process.env.RATE_LIMIT_WINDOW_MS, 15 * 60 * 1000), | ||||
|     max: optionalNumber(process.env.RATE_LIMIT_MAX, 100) | ||||
|   } | ||||
| }; | ||||
|   | ||||
| const required = ['databaseUrl', 'jwtSecret']; | ||||
| const missingKeys = required.filter((key) => !config[key]); | ||||
|   | ||||
| <span class="missing-if-branch" title="if path not taken" >I</span>if (missingKeys.length > 0) { | ||||
| <span class="cstat-no" title="statement not covered" >  throw new Error(`Missing required environment variables: ${missingKeys.join(', ')}`);</span> | ||||
| } | ||||
|   | ||||
| module.exports = config; | ||||
|  </pre></td></tr></table></pre> | ||||
|  | ||||
|                 <div class='push'></div><!-- for sticky footer --> | ||||
|             </div><!-- /wrapper --> | ||||
|             <div class='footer quiet pad2 space-top1 center small'> | ||||
|                 Code coverage generated by | ||||
|                 <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> | ||||
|                 at 2025-10-17T03:41:07.369Z | ||||
|             </div> | ||||
|         <script src="../../prettify.js"></script> | ||||
|         <script> | ||||
|             window.onload = function () { | ||||
|                 prettyPrint(); | ||||
|             }; | ||||
|         </script> | ||||
|         <script src="../../sorter.js"></script> | ||||
|         <script src="../../block-navigation.js"></script> | ||||
|     </body> | ||||
| </html> | ||||
|      | ||||
							
								
								
									
										154
									
								
								backend/coverage/lcov-report/src/database/connection.js.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										154
									
								
								backend/coverage/lcov-report/src/database/connection.js.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,154 @@ | ||||
|  | ||||
| <!doctype html> | ||||
| <html lang="en"> | ||||
|  | ||||
| <head> | ||||
|     <title>Code coverage report for src/database/connection.js</title> | ||||
|     <meta charset="utf-8" /> | ||||
|     <link rel="stylesheet" href="../../prettify.css" /> | ||||
|     <link rel="stylesheet" href="../../base.css" /> | ||||
|     <link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" /> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1" /> | ||||
|     <style type='text/css'> | ||||
|         .coverage-summary .sorter { | ||||
|             background-image: url(../../sort-arrow-sprite.png); | ||||
|         } | ||||
|     </style> | ||||
| </head> | ||||
|      | ||||
| <body> | ||||
| <div class='wrapper'> | ||||
|     <div class='pad1'> | ||||
|         <h1><a href="../../index.html">All files</a> / <a href="index.html">src/database</a> connection.js</h1> | ||||
|         <div class='clearfix'> | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">72.72% </span> | ||||
|                 <span class="quiet">Statements</span> | ||||
|                 <span class='fraction'>8/11</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">25% </span> | ||||
|                 <span class="quiet">Branches</span> | ||||
|                 <span class='fraction'>2/8</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">50% </span> | ||||
|                 <span class="quiet">Functions</span> | ||||
|                 <span class='fraction'>1/2</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">72.72% </span> | ||||
|                 <span class="quiet">Lines</span> | ||||
|                 <span class='fraction'>8/11</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|         </div> | ||||
|         <p class="quiet"> | ||||
|             Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block. | ||||
|         </p> | ||||
|         <template id="filterTemplate"> | ||||
|             <div class="quiet"> | ||||
|                 Filter: | ||||
|                 <input type="search" id="fileSearch"> | ||||
|             </div> | ||||
|         </template> | ||||
|     </div> | ||||
|     <div class='status-line high'></div> | ||||
|     <pre><table class="coverage"> | ||||
| <tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a> | ||||
| <a name='L2'></a><a href='#L2'>2</a> | ||||
| <a name='L3'></a><a href='#L3'>3</a> | ||||
| <a name='L4'></a><a href='#L4'>4</a> | ||||
| <a name='L5'></a><a href='#L5'>5</a> | ||||
| <a name='L6'></a><a href='#L6'>6</a> | ||||
| <a name='L7'></a><a href='#L7'>7</a> | ||||
| <a name='L8'></a><a href='#L8'>8</a> | ||||
| <a name='L9'></a><a href='#L9'>9</a> | ||||
| <a name='L10'></a><a href='#L10'>10</a> | ||||
| <a name='L11'></a><a href='#L11'>11</a> | ||||
| <a name='L12'></a><a href='#L12'>12</a> | ||||
| <a name='L13'></a><a href='#L13'>13</a> | ||||
| <a name='L14'></a><a href='#L14'>14</a> | ||||
| <a name='L15'></a><a href='#L15'>15</a> | ||||
| <a name='L16'></a><a href='#L16'>16</a> | ||||
| <a name='L17'></a><a href='#L17'>17</a> | ||||
| <a name='L18'></a><a href='#L18'>18</a> | ||||
| <a name='L19'></a><a href='#L19'>19</a> | ||||
| <a name='L20'></a><a href='#L20'>20</a> | ||||
| <a name='L21'></a><a href='#L21'>21</a> | ||||
| <a name='L22'></a><a href='#L22'>22</a> | ||||
| <a name='L23'></a><a href='#L23'>23</a> | ||||
| <a name='L24'></a><a href='#L24'>24</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">const { Pool } = require('pg'); | ||||
| const config = require('../config'); | ||||
|   | ||||
| const pool = new Pool({ | ||||
|   connectionString: config.databaseUrl, | ||||
|   ssl: config.env === 'production' ? <span class="branch-0 cbranch-no" title="branch not covered" >{ rejectUnauthorized: false } </span>: false | ||||
| }); | ||||
|   | ||||
| // Test database connection | ||||
| pool.on('connect', () => { | ||||
|   <span class="missing-if-branch" title="else path not taken" >E</span>if (config.logLevel !== 'silent') { | ||||
|     console.info('Connected to MerchantsOfHope-SupplyANdDemandPortal database'); | ||||
|   } | ||||
| }); | ||||
|   | ||||
| pool.on('error', <span class="fstat-no" title="function not covered" >(e</span>rr) => { | ||||
| <span class="cstat-no" title="statement not covered" >  if (err.code === '57P01' || err.message?.includes('terminating connection')) {</span> | ||||
| <span class="cstat-no" title="statement not covered" >    return;</span> | ||||
|   } | ||||
| <span class="cstat-no" title="statement not covered" >  console.error('Database connection error:', err);</span> | ||||
| }); | ||||
|   | ||||
| module.exports = pool; | ||||
|  </pre></td></tr></table></pre> | ||||
|  | ||||
|                 <div class='push'></div><!-- for sticky footer --> | ||||
|             </div><!-- /wrapper --> | ||||
|             <div class='footer quiet pad2 space-top1 center small'> | ||||
|                 Code coverage generated by | ||||
|                 <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> | ||||
|                 at 2025-10-17T03:41:07.369Z | ||||
|             </div> | ||||
|         <script src="../../prettify.js"></script> | ||||
|         <script> | ||||
|             window.onload = function () { | ||||
|                 prettyPrint(); | ||||
|             }; | ||||
|         </script> | ||||
|         <script src="../../sorter.js"></script> | ||||
|         <script src="../../block-navigation.js"></script> | ||||
|     </body> | ||||
| </html> | ||||
|      | ||||
							
								
								
									
										116
									
								
								backend/coverage/lcov-report/src/database/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								backend/coverage/lcov-report/src/database/index.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,116 @@ | ||||
|  | ||||
| <!doctype html> | ||||
| <html lang="en"> | ||||
|  | ||||
| <head> | ||||
|     <title>Code coverage report for src/database</title> | ||||
|     <meta charset="utf-8" /> | ||||
|     <link rel="stylesheet" href="../../prettify.css" /> | ||||
|     <link rel="stylesheet" href="../../base.css" /> | ||||
|     <link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" /> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1" /> | ||||
|     <style type='text/css'> | ||||
|         .coverage-summary .sorter { | ||||
|             background-image: url(../../sort-arrow-sprite.png); | ||||
|         } | ||||
|     </style> | ||||
| </head> | ||||
|      | ||||
| <body> | ||||
| <div class='wrapper'> | ||||
|     <div class='pad1'> | ||||
|         <h1><a href="../../index.html">All files</a> src/database</h1> | ||||
|         <div class='clearfix'> | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">72.72% </span> | ||||
|                 <span class="quiet">Statements</span> | ||||
|                 <span class='fraction'>8/11</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">25% </span> | ||||
|                 <span class="quiet">Branches</span> | ||||
|                 <span class='fraction'>2/8</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">50% </span> | ||||
|                 <span class="quiet">Functions</span> | ||||
|                 <span class='fraction'>1/2</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">72.72% </span> | ||||
|                 <span class="quiet">Lines</span> | ||||
|                 <span class='fraction'>8/11</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|         </div> | ||||
|         <p class="quiet"> | ||||
|             Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block. | ||||
|         </p> | ||||
|         <template id="filterTemplate"> | ||||
|             <div class="quiet"> | ||||
|                 Filter: | ||||
|                 <input type="search" id="fileSearch"> | ||||
|             </div> | ||||
|         </template> | ||||
|     </div> | ||||
|     <div class='status-line high'></div> | ||||
|     <div class="pad1"> | ||||
| <table class="coverage-summary"> | ||||
| <thead> | ||||
| <tr> | ||||
|    <th data-col="file" data-fmt="html" data-html="true" class="file">File</th> | ||||
|    <th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th> | ||||
|    <th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th> | ||||
|    <th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th> | ||||
|    <th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th> | ||||
|    <th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th> | ||||
|    <th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th> | ||||
|    <th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th> | ||||
|    <th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th> | ||||
|    <th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th> | ||||
| </tr> | ||||
| </thead> | ||||
| <tbody><tr> | ||||
| 	<td class="file high" data-value="connection.js"><a href="connection.js.html">connection.js</a></td> | ||||
| 	<td data-value="72.72" class="pic high"> | ||||
| 	<div class="chart"><div class="cover-fill" style="width: 72%"></div><div class="cover-empty" style="width: 28%"></div></div> | ||||
| 	</td> | ||||
| 	<td data-value="72.72" class="pct high">72.72%</td> | ||||
| 	<td data-value="11" class="abs high">8/11</td> | ||||
| 	<td data-value="25" class="pct low">25%</td> | ||||
| 	<td data-value="8" class="abs low">2/8</td> | ||||
| 	<td data-value="50" class="pct medium">50%</td> | ||||
| 	<td data-value="2" class="abs medium">1/2</td> | ||||
| 	<td data-value="72.72" class="pct high">72.72%</td> | ||||
| 	<td data-value="11" class="abs high">8/11</td> | ||||
| 	</tr> | ||||
|  | ||||
| </tbody> | ||||
| </table> | ||||
| </div> | ||||
|                 <div class='push'></div><!-- for sticky footer --> | ||||
|             </div><!-- /wrapper --> | ||||
|             <div class='footer quiet pad2 space-top1 center small'> | ||||
|                 Code coverage generated by | ||||
|                 <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> | ||||
|                 at 2025-10-17T03:41:07.369Z | ||||
|             </div> | ||||
|         <script src="../../prettify.js"></script> | ||||
|         <script> | ||||
|             window.onload = function () { | ||||
|                 prettyPrint(); | ||||
|             }; | ||||
|         </script> | ||||
|         <script src="../../sorter.js"></script> | ||||
|         <script src="../../block-navigation.js"></script> | ||||
|     </body> | ||||
| </html> | ||||
|      | ||||
							
								
								
									
										116
									
								
								backend/coverage/lcov-report/src/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								backend/coverage/lcov-report/src/index.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,116 @@ | ||||
|  | ||||
| <!doctype html> | ||||
| <html lang="en"> | ||||
|  | ||||
| <head> | ||||
|     <title>Code coverage report for src</title> | ||||
|     <meta charset="utf-8" /> | ||||
|     <link rel="stylesheet" href="../prettify.css" /> | ||||
|     <link rel="stylesheet" href="../base.css" /> | ||||
|     <link rel="shortcut icon" type="image/x-icon" href="../favicon.png" /> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1" /> | ||||
|     <style type='text/css'> | ||||
|         .coverage-summary .sorter { | ||||
|             background-image: url(../sort-arrow-sprite.png); | ||||
|         } | ||||
|     </style> | ||||
| </head> | ||||
|      | ||||
| <body> | ||||
| <div class='wrapper'> | ||||
|     <div class='pad1'> | ||||
|         <h1><a href="../index.html">All files</a> src</h1> | ||||
|         <div class='clearfix'> | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">86.84% </span> | ||||
|                 <span class="quiet">Statements</span> | ||||
|                 <span class='fraction'>33/38</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">37.5% </span> | ||||
|                 <span class="quiet">Branches</span> | ||||
|                 <span class='fraction'>3/8</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">0% </span> | ||||
|                 <span class="quiet">Functions</span> | ||||
|                 <span class='fraction'>0/4</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">86.84% </span> | ||||
|                 <span class="quiet">Lines</span> | ||||
|                 <span class='fraction'>33/38</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|         </div> | ||||
|         <p class="quiet"> | ||||
|             Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block. | ||||
|         </p> | ||||
|         <template id="filterTemplate"> | ||||
|             <div class="quiet"> | ||||
|                 Filter: | ||||
|                 <input type="search" id="fileSearch"> | ||||
|             </div> | ||||
|         </template> | ||||
|     </div> | ||||
|     <div class='status-line high'></div> | ||||
|     <div class="pad1"> | ||||
| <table class="coverage-summary"> | ||||
| <thead> | ||||
| <tr> | ||||
|    <th data-col="file" data-fmt="html" data-html="true" class="file">File</th> | ||||
|    <th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th> | ||||
|    <th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th> | ||||
|    <th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th> | ||||
|    <th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th> | ||||
|    <th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th> | ||||
|    <th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th> | ||||
|    <th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th> | ||||
|    <th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th> | ||||
|    <th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th> | ||||
| </tr> | ||||
| </thead> | ||||
| <tbody><tr> | ||||
| 	<td class="file high" data-value="server.js"><a href="server.js.html">server.js</a></td> | ||||
| 	<td data-value="86.84" class="pic high"> | ||||
| 	<div class="chart"><div class="cover-fill" style="width: 86%"></div><div class="cover-empty" style="width: 14%"></div></div> | ||||
| 	</td> | ||||
| 	<td data-value="86.84" class="pct high">86.84%</td> | ||||
| 	<td data-value="38" class="abs high">33/38</td> | ||||
| 	<td data-value="37.5" class="pct low">37.5%</td> | ||||
| 	<td data-value="8" class="abs low">3/8</td> | ||||
| 	<td data-value="0" class="pct low">0%</td> | ||||
| 	<td data-value="4" class="abs low">0/4</td> | ||||
| 	<td data-value="86.84" class="pct high">86.84%</td> | ||||
| 	<td data-value="38" class="abs high">33/38</td> | ||||
| 	</tr> | ||||
|  | ||||
| </tbody> | ||||
| </table> | ||||
| </div> | ||||
|                 <div class='push'></div><!-- for sticky footer --> | ||||
|             </div><!-- /wrapper --> | ||||
|             <div class='footer quiet pad2 space-top1 center small'> | ||||
|                 Code coverage generated by | ||||
|                 <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> | ||||
|                 at 2025-10-17T03:41:07.369Z | ||||
|             </div> | ||||
|         <script src="../prettify.js"></script> | ||||
|         <script> | ||||
|             window.onload = function () { | ||||
|                 prettyPrint(); | ||||
|             }; | ||||
|         </script> | ||||
|         <script src="../sorter.js"></script> | ||||
|         <script src="../block-navigation.js"></script> | ||||
|     </body> | ||||
| </html> | ||||
|      | ||||
							
								
								
									
										250
									
								
								backend/coverage/lcov-report/src/middleware/auth.js.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										250
									
								
								backend/coverage/lcov-report/src/middleware/auth.js.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,250 @@ | ||||
|  | ||||
| <!doctype html> | ||||
| <html lang="en"> | ||||
|  | ||||
| <head> | ||||
|     <title>Code coverage report for src/middleware/auth.js</title> | ||||
|     <meta charset="utf-8" /> | ||||
|     <link rel="stylesheet" href="../../prettify.css" /> | ||||
|     <link rel="stylesheet" href="../../base.css" /> | ||||
|     <link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" /> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1" /> | ||||
|     <style type='text/css'> | ||||
|         .coverage-summary .sorter { | ||||
|             background-image: url(../../sort-arrow-sprite.png); | ||||
|         } | ||||
|     </style> | ||||
| </head> | ||||
|      | ||||
| <body> | ||||
| <div class='wrapper'> | ||||
|     <div class='pad1'> | ||||
|         <h1><a href="../../index.html">All files</a> / <a href="index.html">src/middleware</a> auth.js</h1> | ||||
|         <div class='clearfix'> | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">85.18% </span> | ||||
|                 <span class="quiet">Statements</span> | ||||
|                 <span class='fraction'>23/27</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">66.66% </span> | ||||
|                 <span class="quiet">Branches</span> | ||||
|                 <span class='fraction'>8/12</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">100% </span> | ||||
|                 <span class="quiet">Functions</span> | ||||
|                 <span class='fraction'>3/3</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">85.18% </span> | ||||
|                 <span class="quiet">Lines</span> | ||||
|                 <span class='fraction'>23/27</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|         </div> | ||||
|         <p class="quiet"> | ||||
|             Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block. | ||||
|         </p> | ||||
|         <template id="filterTemplate"> | ||||
|             <div class="quiet"> | ||||
|                 Filter: | ||||
|                 <input type="search" id="fileSearch"> | ||||
|             </div> | ||||
|         </template> | ||||
|     </div> | ||||
|     <div class='status-line high'></div> | ||||
|     <pre><table class="coverage"> | ||||
| <tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a> | ||||
| <a name='L2'></a><a href='#L2'>2</a> | ||||
| <a name='L3'></a><a href='#L3'>3</a> | ||||
| <a name='L4'></a><a href='#L4'>4</a> | ||||
| <a name='L5'></a><a href='#L5'>5</a> | ||||
| <a name='L6'></a><a href='#L6'>6</a> | ||||
| <a name='L7'></a><a href='#L7'>7</a> | ||||
| <a name='L8'></a><a href='#L8'>8</a> | ||||
| <a name='L9'></a><a href='#L9'>9</a> | ||||
| <a name='L10'></a><a href='#L10'>10</a> | ||||
| <a name='L11'></a><a href='#L11'>11</a> | ||||
| <a name='L12'></a><a href='#L12'>12</a> | ||||
| <a name='L13'></a><a href='#L13'>13</a> | ||||
| <a name='L14'></a><a href='#L14'>14</a> | ||||
| <a name='L15'></a><a href='#L15'>15</a> | ||||
| <a name='L16'></a><a href='#L16'>16</a> | ||||
| <a name='L17'></a><a href='#L17'>17</a> | ||||
| <a name='L18'></a><a href='#L18'>18</a> | ||||
| <a name='L19'></a><a href='#L19'>19</a> | ||||
| <a name='L20'></a><a href='#L20'>20</a> | ||||
| <a name='L21'></a><a href='#L21'>21</a> | ||||
| <a name='L22'></a><a href='#L22'>22</a> | ||||
| <a name='L23'></a><a href='#L23'>23</a> | ||||
| <a name='L24'></a><a href='#L24'>24</a> | ||||
| <a name='L25'></a><a href='#L25'>25</a> | ||||
| <a name='L26'></a><a href='#L26'>26</a> | ||||
| <a name='L27'></a><a href='#L27'>27</a> | ||||
| <a name='L28'></a><a href='#L28'>28</a> | ||||
| <a name='L29'></a><a href='#L29'>29</a> | ||||
| <a name='L30'></a><a href='#L30'>30</a> | ||||
| <a name='L31'></a><a href='#L31'>31</a> | ||||
| <a name='L32'></a><a href='#L32'>32</a> | ||||
| <a name='L33'></a><a href='#L33'>33</a> | ||||
| <a name='L34'></a><a href='#L34'>34</a> | ||||
| <a name='L35'></a><a href='#L35'>35</a> | ||||
| <a name='L36'></a><a href='#L36'>36</a> | ||||
| <a name='L37'></a><a href='#L37'>37</a> | ||||
| <a name='L38'></a><a href='#L38'>38</a> | ||||
| <a name='L39'></a><a href='#L39'>39</a> | ||||
| <a name='L40'></a><a href='#L40'>40</a> | ||||
| <a name='L41'></a><a href='#L41'>41</a> | ||||
| <a name='L42'></a><a href='#L42'>42</a> | ||||
| <a name='L43'></a><a href='#L43'>43</a> | ||||
| <a name='L44'></a><a href='#L44'>44</a> | ||||
| <a name='L45'></a><a href='#L45'>45</a> | ||||
| <a name='L46'></a><a href='#L46'>46</a> | ||||
| <a name='L47'></a><a href='#L47'>47</a> | ||||
| <a name='L48'></a><a href='#L48'>48</a> | ||||
| <a name='L49'></a><a href='#L49'>49</a> | ||||
| <a name='L50'></a><a href='#L50'>50</a> | ||||
| <a name='L51'></a><a href='#L51'>51</a> | ||||
| <a name='L52'></a><a href='#L52'>52</a> | ||||
| <a name='L53'></a><a href='#L53'>53</a> | ||||
| <a name='L54'></a><a href='#L54'>54</a> | ||||
| <a name='L55'></a><a href='#L55'>55</a> | ||||
| <a name='L56'></a><a href='#L56'>56</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">53x</span> | ||||
| <span class="cline-any cline-yes">53x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">53x</span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">52x</span> | ||||
| <span class="cline-any cline-yes">52x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">51x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">51x</span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">51x</span> | ||||
| <span class="cline-any cline-yes">51x</span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">51x</span> | ||||
| <span class="cline-any cline-yes">51x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">70x</span> | ||||
| <span class="cline-any cline-yes">31x</span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">31x</span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">31x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">const jwt = require('jsonwebtoken'); | ||||
| const pool = require('../database/connection'); | ||||
| const config = require('../config'); | ||||
|   | ||||
| const authenticateToken = async (req, res, next) => { | ||||
|   const authHeader = req.headers['authorization']; | ||||
|   const token = authHeader && authHeader.split(' ')[1]; | ||||
|   | ||||
|   if (!token) { | ||||
|     return res.status(401).json({ error: 'Access token required' }); | ||||
|   } | ||||
|   | ||||
|   try { | ||||
|     const decoded = jwt.verify(token, config.jwtSecret); | ||||
|      | ||||
|     // Get user details from database | ||||
|     const userResult = await pool.query( | ||||
|       'SELECT id, email, first_name, last_name, role, is_active FROM users WHERE id = $1', | ||||
|       [decoded.userId] | ||||
|     ); | ||||
|   | ||||
|     <span class="missing-if-branch" title="if path not taken" >I</span>if (userResult.rows.length === 0) { | ||||
| <span class="cstat-no" title="statement not covered" >      return res.status(401).json({ error: 'Invalid token' });</span> | ||||
|     } | ||||
|   | ||||
|     const user = userResult.rows[0]; | ||||
|     <span class="missing-if-branch" title="if path not taken" >I</span>if (!user.is_active) { | ||||
| <span class="cstat-no" title="statement not covered" >      return res.status(401).json({ error: 'Account deactivated' });</span> | ||||
|     } | ||||
|   | ||||
|     req.user = user; | ||||
|     next(); | ||||
|   } catch (error) { | ||||
|     return res.status(403).json({ error: 'Invalid or expired token' }); | ||||
|   } | ||||
| }; | ||||
|   | ||||
| const requireRole = (roles) => { | ||||
|   return (req, res, next) => { | ||||
|     <span class="missing-if-branch" title="if path not taken" >I</span>if (!req.user) { | ||||
| <span class="cstat-no" title="statement not covered" >      return res.status(401).json({ error: 'Authentication required' });</span> | ||||
|     } | ||||
|   | ||||
|     <span class="missing-if-branch" title="if path not taken" >I</span>if (!roles.includes(req.user.role)) { | ||||
| <span class="cstat-no" title="statement not covered" >      return res.status(403).json({ error: 'Insufficient permissions' });</span> | ||||
|     } | ||||
|   | ||||
|     next(); | ||||
|   }; | ||||
| }; | ||||
|   | ||||
| module.exports = { | ||||
|   authenticateToken, | ||||
|   requireRole | ||||
| }; | ||||
|  </pre></td></tr></table></pre> | ||||
|  | ||||
|                 <div class='push'></div><!-- for sticky footer --> | ||||
|             </div><!-- /wrapper --> | ||||
|             <div class='footer quiet pad2 space-top1 center small'> | ||||
|                 Code coverage generated by | ||||
|                 <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> | ||||
|                 at 2025-10-17T03:41:07.369Z | ||||
|             </div> | ||||
|         <script src="../../prettify.js"></script> | ||||
|         <script> | ||||
|             window.onload = function () { | ||||
|                 prettyPrint(); | ||||
|             }; | ||||
|         </script> | ||||
|         <script src="../../sorter.js"></script> | ||||
|         <script src="../../block-navigation.js"></script> | ||||
|     </body> | ||||
| </html> | ||||
|      | ||||
							
								
								
									
										116
									
								
								backend/coverage/lcov-report/src/middleware/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								backend/coverage/lcov-report/src/middleware/index.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,116 @@ | ||||
|  | ||||
| <!doctype html> | ||||
| <html lang="en"> | ||||
|  | ||||
| <head> | ||||
|     <title>Code coverage report for src/middleware</title> | ||||
|     <meta charset="utf-8" /> | ||||
|     <link rel="stylesheet" href="../../prettify.css" /> | ||||
|     <link rel="stylesheet" href="../../base.css" /> | ||||
|     <link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" /> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1" /> | ||||
|     <style type='text/css'> | ||||
|         .coverage-summary .sorter { | ||||
|             background-image: url(../../sort-arrow-sprite.png); | ||||
|         } | ||||
|     </style> | ||||
| </head> | ||||
|      | ||||
| <body> | ||||
| <div class='wrapper'> | ||||
|     <div class='pad1'> | ||||
|         <h1><a href="../../index.html">All files</a> src/middleware</h1> | ||||
|         <div class='clearfix'> | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">85.18% </span> | ||||
|                 <span class="quiet">Statements</span> | ||||
|                 <span class='fraction'>23/27</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">66.66% </span> | ||||
|                 <span class="quiet">Branches</span> | ||||
|                 <span class='fraction'>8/12</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">100% </span> | ||||
|                 <span class="quiet">Functions</span> | ||||
|                 <span class='fraction'>3/3</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">85.18% </span> | ||||
|                 <span class="quiet">Lines</span> | ||||
|                 <span class='fraction'>23/27</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|         </div> | ||||
|         <p class="quiet"> | ||||
|             Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block. | ||||
|         </p> | ||||
|         <template id="filterTemplate"> | ||||
|             <div class="quiet"> | ||||
|                 Filter: | ||||
|                 <input type="search" id="fileSearch"> | ||||
|             </div> | ||||
|         </template> | ||||
|     </div> | ||||
|     <div class='status-line high'></div> | ||||
|     <div class="pad1"> | ||||
| <table class="coverage-summary"> | ||||
| <thead> | ||||
| <tr> | ||||
|    <th data-col="file" data-fmt="html" data-html="true" class="file">File</th> | ||||
|    <th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th> | ||||
|    <th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th> | ||||
|    <th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th> | ||||
|    <th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th> | ||||
|    <th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th> | ||||
|    <th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th> | ||||
|    <th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th> | ||||
|    <th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th> | ||||
|    <th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th> | ||||
| </tr> | ||||
| </thead> | ||||
| <tbody><tr> | ||||
| 	<td class="file high" data-value="auth.js"><a href="auth.js.html">auth.js</a></td> | ||||
| 	<td data-value="85.18" class="pic high"> | ||||
| 	<div class="chart"><div class="cover-fill" style="width: 85%"></div><div class="cover-empty" style="width: 15%"></div></div> | ||||
| 	</td> | ||||
| 	<td data-value="85.18" class="pct high">85.18%</td> | ||||
| 	<td data-value="27" class="abs high">23/27</td> | ||||
| 	<td data-value="66.66" class="pct high">66.66%</td> | ||||
| 	<td data-value="12" class="abs high">8/12</td> | ||||
| 	<td data-value="100" class="pct high">100%</td> | ||||
| 	<td data-value="3" class="abs high">3/3</td> | ||||
| 	<td data-value="85.18" class="pct high">85.18%</td> | ||||
| 	<td data-value="27" class="abs high">23/27</td> | ||||
| 	</tr> | ||||
|  | ||||
| </tbody> | ||||
| </table> | ||||
| </div> | ||||
|                 <div class='push'></div><!-- for sticky footer --> | ||||
|             </div><!-- /wrapper --> | ||||
|             <div class='footer quiet pad2 space-top1 center small'> | ||||
|                 Code coverage generated by | ||||
|                 <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> | ||||
|                 at 2025-10-17T03:41:07.369Z | ||||
|             </div> | ||||
|         <script src="../../prettify.js"></script> | ||||
|         <script> | ||||
|             window.onload = function () { | ||||
|                 prettyPrint(); | ||||
|             }; | ||||
|         </script> | ||||
|         <script src="../../sorter.js"></script> | ||||
|         <script src="../../block-navigation.js"></script> | ||||
|     </body> | ||||
| </html> | ||||
|      | ||||
							
								
								
									
										1357
									
								
								backend/coverage/lcov-report/src/routes/applications.js.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1357
									
								
								backend/coverage/lcov-report/src/routes/applications.js.html
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										547
									
								
								backend/coverage/lcov-report/src/routes/auth.js.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										547
									
								
								backend/coverage/lcov-report/src/routes/auth.js.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,547 @@ | ||||
|  | ||||
| <!doctype html> | ||||
| <html lang="en"> | ||||
|  | ||||
| <head> | ||||
|     <title>Code coverage report for src/routes/auth.js</title> | ||||
|     <meta charset="utf-8" /> | ||||
|     <link rel="stylesheet" href="../../prettify.css" /> | ||||
|     <link rel="stylesheet" href="../../base.css" /> | ||||
|     <link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" /> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1" /> | ||||
|     <style type='text/css'> | ||||
|         .coverage-summary .sorter { | ||||
|             background-image: url(../../sort-arrow-sprite.png); | ||||
|         } | ||||
|     </style> | ||||
| </head> | ||||
|      | ||||
| <body> | ||||
| <div class='wrapper'> | ||||
|     <div class='pad1'> | ||||
|         <h1><a href="../../index.html">All files</a> / <a href="index.html">src/routes</a> auth.js</h1> | ||||
|         <div class='clearfix'> | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">82.35% </span> | ||||
|                 <span class="quiet">Statements</span> | ||||
|                 <span class='fraction'>42/51</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">83.33% </span> | ||||
|                 <span class="quiet">Branches</span> | ||||
|                 <span class='fraction'>10/12</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">75% </span> | ||||
|                 <span class="quiet">Functions</span> | ||||
|                 <span class='fraction'>3/4</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">82.35% </span> | ||||
|                 <span class="quiet">Lines</span> | ||||
|                 <span class='fraction'>42/51</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|         </div> | ||||
|         <p class="quiet"> | ||||
|             Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block. | ||||
|         </p> | ||||
|         <template id="filterTemplate"> | ||||
|             <div class="quiet"> | ||||
|                 Filter: | ||||
|                 <input type="search" id="fileSearch"> | ||||
|             </div> | ||||
|         </template> | ||||
|     </div> | ||||
|     <div class='status-line high'></div> | ||||
|     <pre><table class="coverage"> | ||||
| <tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a> | ||||
| <a name='L2'></a><a href='#L2'>2</a> | ||||
| <a name='L3'></a><a href='#L3'>3</a> | ||||
| <a name='L4'></a><a href='#L4'>4</a> | ||||
| <a name='L5'></a><a href='#L5'>5</a> | ||||
| <a name='L6'></a><a href='#L6'>6</a> | ||||
| <a name='L7'></a><a href='#L7'>7</a> | ||||
| <a name='L8'></a><a href='#L8'>8</a> | ||||
| <a name='L9'></a><a href='#L9'>9</a> | ||||
| <a name='L10'></a><a href='#L10'>10</a> | ||||
| <a name='L11'></a><a href='#L11'>11</a> | ||||
| <a name='L12'></a><a href='#L12'>12</a> | ||||
| <a name='L13'></a><a href='#L13'>13</a> | ||||
| <a name='L14'></a><a href='#L14'>14</a> | ||||
| <a name='L15'></a><a href='#L15'>15</a> | ||||
| <a name='L16'></a><a href='#L16'>16</a> | ||||
| <a name='L17'></a><a href='#L17'>17</a> | ||||
| <a name='L18'></a><a href='#L18'>18</a> | ||||
| <a name='L19'></a><a href='#L19'>19</a> | ||||
| <a name='L20'></a><a href='#L20'>20</a> | ||||
| <a name='L21'></a><a href='#L21'>21</a> | ||||
| <a name='L22'></a><a href='#L22'>22</a> | ||||
| <a name='L23'></a><a href='#L23'>23</a> | ||||
| <a name='L24'></a><a href='#L24'>24</a> | ||||
| <a name='L25'></a><a href='#L25'>25</a> | ||||
| <a name='L26'></a><a href='#L26'>26</a> | ||||
| <a name='L27'></a><a href='#L27'>27</a> | ||||
| <a name='L28'></a><a href='#L28'>28</a> | ||||
| <a name='L29'></a><a href='#L29'>29</a> | ||||
| <a name='L30'></a><a href='#L30'>30</a> | ||||
| <a name='L31'></a><a href='#L31'>31</a> | ||||
| <a name='L32'></a><a href='#L32'>32</a> | ||||
| <a name='L33'></a><a href='#L33'>33</a> | ||||
| <a name='L34'></a><a href='#L34'>34</a> | ||||
| <a name='L35'></a><a href='#L35'>35</a> | ||||
| <a name='L36'></a><a href='#L36'>36</a> | ||||
| <a name='L37'></a><a href='#L37'>37</a> | ||||
| <a name='L38'></a><a href='#L38'>38</a> | ||||
| <a name='L39'></a><a href='#L39'>39</a> | ||||
| <a name='L40'></a><a href='#L40'>40</a> | ||||
| <a name='L41'></a><a href='#L41'>41</a> | ||||
| <a name='L42'></a><a href='#L42'>42</a> | ||||
| <a name='L43'></a><a href='#L43'>43</a> | ||||
| <a name='L44'></a><a href='#L44'>44</a> | ||||
| <a name='L45'></a><a href='#L45'>45</a> | ||||
| <a name='L46'></a><a href='#L46'>46</a> | ||||
| <a name='L47'></a><a href='#L47'>47</a> | ||||
| <a name='L48'></a><a href='#L48'>48</a> | ||||
| <a name='L49'></a><a href='#L49'>49</a> | ||||
| <a name='L50'></a><a href='#L50'>50</a> | ||||
| <a name='L51'></a><a href='#L51'>51</a> | ||||
| <a name='L52'></a><a href='#L52'>52</a> | ||||
| <a name='L53'></a><a href='#L53'>53</a> | ||||
| <a name='L54'></a><a href='#L54'>54</a> | ||||
| <a name='L55'></a><a href='#L55'>55</a> | ||||
| <a name='L56'></a><a href='#L56'>56</a> | ||||
| <a name='L57'></a><a href='#L57'>57</a> | ||||
| <a name='L58'></a><a href='#L58'>58</a> | ||||
| <a name='L59'></a><a href='#L59'>59</a> | ||||
| <a name='L60'></a><a href='#L60'>60</a> | ||||
| <a name='L61'></a><a href='#L61'>61</a> | ||||
| <a name='L62'></a><a href='#L62'>62</a> | ||||
| <a name='L63'></a><a href='#L63'>63</a> | ||||
| <a name='L64'></a><a href='#L64'>64</a> | ||||
| <a name='L65'></a><a href='#L65'>65</a> | ||||
| <a name='L66'></a><a href='#L66'>66</a> | ||||
| <a name='L67'></a><a href='#L67'>67</a> | ||||
| <a name='L68'></a><a href='#L68'>68</a> | ||||
| <a name='L69'></a><a href='#L69'>69</a> | ||||
| <a name='L70'></a><a href='#L70'>70</a> | ||||
| <a name='L71'></a><a href='#L71'>71</a> | ||||
| <a name='L72'></a><a href='#L72'>72</a> | ||||
| <a name='L73'></a><a href='#L73'>73</a> | ||||
| <a name='L74'></a><a href='#L74'>74</a> | ||||
| <a name='L75'></a><a href='#L75'>75</a> | ||||
| <a name='L76'></a><a href='#L76'>76</a> | ||||
| <a name='L77'></a><a href='#L77'>77</a> | ||||
| <a name='L78'></a><a href='#L78'>78</a> | ||||
| <a name='L79'></a><a href='#L79'>79</a> | ||||
| <a name='L80'></a><a href='#L80'>80</a> | ||||
| <a name='L81'></a><a href='#L81'>81</a> | ||||
| <a name='L82'></a><a href='#L82'>82</a> | ||||
| <a name='L83'></a><a href='#L83'>83</a> | ||||
| <a name='L84'></a><a href='#L84'>84</a> | ||||
| <a name='L85'></a><a href='#L85'>85</a> | ||||
| <a name='L86'></a><a href='#L86'>86</a> | ||||
| <a name='L87'></a><a href='#L87'>87</a> | ||||
| <a name='L88'></a><a href='#L88'>88</a> | ||||
| <a name='L89'></a><a href='#L89'>89</a> | ||||
| <a name='L90'></a><a href='#L90'>90</a> | ||||
| <a name='L91'></a><a href='#L91'>91</a> | ||||
| <a name='L92'></a><a href='#L92'>92</a> | ||||
| <a name='L93'></a><a href='#L93'>93</a> | ||||
| <a name='L94'></a><a href='#L94'>94</a> | ||||
| <a name='L95'></a><a href='#L95'>95</a> | ||||
| <a name='L96'></a><a href='#L96'>96</a> | ||||
| <a name='L97'></a><a href='#L97'>97</a> | ||||
| <a name='L98'></a><a href='#L98'>98</a> | ||||
| <a name='L99'></a><a href='#L99'>99</a> | ||||
| <a name='L100'></a><a href='#L100'>100</a> | ||||
| <a name='L101'></a><a href='#L101'>101</a> | ||||
| <a name='L102'></a><a href='#L102'>102</a> | ||||
| <a name='L103'></a><a href='#L103'>103</a> | ||||
| <a name='L104'></a><a href='#L104'>104</a> | ||||
| <a name='L105'></a><a href='#L105'>105</a> | ||||
| <a name='L106'></a><a href='#L106'>106</a> | ||||
| <a name='L107'></a><a href='#L107'>107</a> | ||||
| <a name='L108'></a><a href='#L108'>108</a> | ||||
| <a name='L109'></a><a href='#L109'>109</a> | ||||
| <a name='L110'></a><a href='#L110'>110</a> | ||||
| <a name='L111'></a><a href='#L111'>111</a> | ||||
| <a name='L112'></a><a href='#L112'>112</a> | ||||
| <a name='L113'></a><a href='#L113'>113</a> | ||||
| <a name='L114'></a><a href='#L114'>114</a> | ||||
| <a name='L115'></a><a href='#L115'>115</a> | ||||
| <a name='L116'></a><a href='#L116'>116</a> | ||||
| <a name='L117'></a><a href='#L117'>117</a> | ||||
| <a name='L118'></a><a href='#L118'>118</a> | ||||
| <a name='L119'></a><a href='#L119'>119</a> | ||||
| <a name='L120'></a><a href='#L120'>120</a> | ||||
| <a name='L121'></a><a href='#L121'>121</a> | ||||
| <a name='L122'></a><a href='#L122'>122</a> | ||||
| <a name='L123'></a><a href='#L123'>123</a> | ||||
| <a name='L124'></a><a href='#L124'>124</a> | ||||
| <a name='L125'></a><a href='#L125'>125</a> | ||||
| <a name='L126'></a><a href='#L126'>126</a> | ||||
| <a name='L127'></a><a href='#L127'>127</a> | ||||
| <a name='L128'></a><a href='#L128'>128</a> | ||||
| <a name='L129'></a><a href='#L129'>129</a> | ||||
| <a name='L130'></a><a href='#L130'>130</a> | ||||
| <a name='L131'></a><a href='#L131'>131</a> | ||||
| <a name='L132'></a><a href='#L132'>132</a> | ||||
| <a name='L133'></a><a href='#L133'>133</a> | ||||
| <a name='L134'></a><a href='#L134'>134</a> | ||||
| <a name='L135'></a><a href='#L135'>135</a> | ||||
| <a name='L136'></a><a href='#L136'>136</a> | ||||
| <a name='L137'></a><a href='#L137'>137</a> | ||||
| <a name='L138'></a><a href='#L138'>138</a> | ||||
| <a name='L139'></a><a href='#L139'>139</a> | ||||
| <a name='L140'></a><a href='#L140'>140</a> | ||||
| <a name='L141'></a><a href='#L141'>141</a> | ||||
| <a name='L142'></a><a href='#L142'>142</a> | ||||
| <a name='L143'></a><a href='#L143'>143</a> | ||||
| <a name='L144'></a><a href='#L144'>144</a> | ||||
| <a name='L145'></a><a href='#L145'>145</a> | ||||
| <a name='L146'></a><a href='#L146'>146</a> | ||||
| <a name='L147'></a><a href='#L147'>147</a> | ||||
| <a name='L148'></a><a href='#L148'>148</a> | ||||
| <a name='L149'></a><a href='#L149'>149</a> | ||||
| <a name='L150'></a><a href='#L150'>150</a> | ||||
| <a name='L151'></a><a href='#L151'>151</a> | ||||
| <a name='L152'></a><a href='#L152'>152</a> | ||||
| <a name='L153'></a><a href='#L153'>153</a> | ||||
| <a name='L154'></a><a href='#L154'>154</a> | ||||
| <a name='L155'></a><a href='#L155'>155</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">25x</span> | ||||
| <span class="cline-any cline-yes">25x</span> | ||||
| <span class="cline-any cline-yes">25x</span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">24x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">24x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">24x</span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">23x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">23x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">23x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">23x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">23x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">4x</span> | ||||
| <span class="cline-any cline-yes">4x</span> | ||||
| <span class="cline-any cline-yes">4x</span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">4x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">4x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">4x</span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">3x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">3x</span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">3x</span> | ||||
| <span class="cline-any cline-yes">3x</span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">2x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">2x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">const express = require('express'); | ||||
| const bcrypt = require('bcryptjs'); | ||||
| const jwt = require('jsonwebtoken'); | ||||
| const { body, validationResult } = require('express-validator'); | ||||
| const pool = require('../database/connection'); | ||||
| const { authenticateToken } = require('../middleware/auth'); | ||||
| const config = require('../config'); | ||||
|   | ||||
| const router = express.Router(); | ||||
|   | ||||
| // Register | ||||
| router.post('/register', [ | ||||
|   body('email').isEmail().normalizeEmail(), | ||||
|   body('password').isLength({ min: 6 }), | ||||
|   body('firstName').notEmpty().trim(), | ||||
|   body('lastName').notEmpty().trim(), | ||||
|   body('role').isIn(['recruiter', 'employer', 'candidate']) | ||||
| ], async (req, res) => { | ||||
|   try { | ||||
|     const errors = validationResult(req); | ||||
|     if (!errors.isEmpty()) { | ||||
|       return res.status(400).json({ errors: errors.array() }); | ||||
|     } | ||||
|   | ||||
|     const { email, password, firstName, lastName, role } = req.body; | ||||
|   | ||||
|     // Check if user already exists | ||||
|     const existingUser = await pool.query( | ||||
|       'SELECT id FROM users WHERE email = $1', | ||||
|       [email] | ||||
|     ); | ||||
|   | ||||
|     if (existingUser.rows.length > 0) { | ||||
|       return res.status(400).json({ error: 'User already exists' }); | ||||
|     } | ||||
|   | ||||
|     // Hash password | ||||
|     const passwordHash = await bcrypt.hash(password, 10); | ||||
|   | ||||
|     // Create user | ||||
|     const userResult = await pool.query( | ||||
|       'INSERT INTO users (email, password_hash, first_name, last_name, role) VALUES ($1, $2, $3, $4, $5) RETURNING id, email, first_name, last_name, role', | ||||
|       [email, passwordHash, firstName, lastName, role] | ||||
|     ); | ||||
|   | ||||
|     const user = userResult.rows[0]; | ||||
|   | ||||
|     // Generate JWT token | ||||
|     const token = jwt.sign( | ||||
|       { userId: user.id, email: user.email, role: user.role }, | ||||
|       config.jwtSecret, | ||||
|       { expiresIn: '24h' } | ||||
|     ); | ||||
|   | ||||
|     res.status(201).json({ | ||||
|       message: 'User created successfully', | ||||
|       token, | ||||
|       user: { | ||||
|         id: user.id, | ||||
|         email: user.email, | ||||
|         firstName: user.first_name, | ||||
|         lastName: user.last_name, | ||||
|         role: user.role | ||||
|       } | ||||
|     }); | ||||
|   } catch (error) { | ||||
| <span class="cstat-no" title="statement not covered" >    console.error('Registration error:', error);</span> | ||||
| <span class="cstat-no" title="statement not covered" >    res.status(500).json({ error: 'Registration failed' });</span> | ||||
|   } | ||||
| }); | ||||
|   | ||||
| // Login | ||||
| router.post('/login', [ | ||||
|   body('email').isEmail().normalizeEmail(), | ||||
|   body('password').notEmpty() | ||||
| ], async (req, res) => { | ||||
|   try { | ||||
|     const errors = validationResult(req); | ||||
|     <span class="missing-if-branch" title="if path not taken" >I</span>if (!errors.isEmpty()) { | ||||
| <span class="cstat-no" title="statement not covered" >      return res.status(400).json({ errors: errors.array() });</span> | ||||
|     } | ||||
|   | ||||
|     const { email, password } = req.body; | ||||
|   | ||||
|     // Get user | ||||
|     const userResult = await pool.query( | ||||
|       'SELECT id, email, password_hash, first_name, last_name, role, is_active FROM users WHERE email = $1', | ||||
|       [email] | ||||
|     ); | ||||
|   | ||||
|     if (userResult.rows.length === 0) { | ||||
|       return res.status(401).json({ error: 'Invalid credentials' }); | ||||
|     } | ||||
|   | ||||
|     const user = userResult.rows[0]; | ||||
|   | ||||
|     <span class="missing-if-branch" title="if path not taken" >I</span>if (!user.is_active) { | ||||
| <span class="cstat-no" title="statement not covered" >      return res.status(401).json({ error: 'Account deactivated' });</span> | ||||
|     } | ||||
|   | ||||
|     // Verify password | ||||
|     const isValidPassword = await bcrypt.compare(password, user.password_hash); | ||||
|     if (!isValidPassword) { | ||||
|       return res.status(401).json({ error: 'Invalid credentials' }); | ||||
|     } | ||||
|   | ||||
|     // Generate JWT token | ||||
|     const token = jwt.sign( | ||||
|       { userId: user.id, email: user.email, role: user.role }, | ||||
|       config.jwtSecret, | ||||
|       { expiresIn: '24h' } | ||||
|     ); | ||||
|   | ||||
|     res.json({ | ||||
|       message: 'Login successful', | ||||
|       token, | ||||
|       user: { | ||||
|         id: user.id, | ||||
|         email: user.email, | ||||
|         firstName: user.first_name, | ||||
|         lastName: user.last_name, | ||||
|         role: user.role | ||||
|       } | ||||
|     }); | ||||
|   } catch (error) { | ||||
| <span class="cstat-no" title="statement not covered" >    console.error('Login error:', error);</span> | ||||
| <span class="cstat-no" title="statement not covered" >    res.status(500).json({ error: 'Login failed' });</span> | ||||
|   } | ||||
| }); | ||||
|   | ||||
| // Get current user | ||||
| router.get('/me', authenticateToken, async (req, res) => { | ||||
|   try { | ||||
|     res.json({ | ||||
|       user: { | ||||
|         id: req.user.id, | ||||
|         email: req.user.email, | ||||
|         firstName: req.user.first_name, | ||||
|         lastName: req.user.last_name, | ||||
|         role: req.user.role | ||||
|       } | ||||
|     }); | ||||
|   } catch (error) { | ||||
| <span class="cstat-no" title="statement not covered" >    console.error('Get user error:', error);</span> | ||||
| <span class="cstat-no" title="statement not covered" >    res.status(500).json({ error: 'Failed to get user information' });</span> | ||||
|   } | ||||
| }); | ||||
|   | ||||
| // Logout (client-side token removal) | ||||
| router.post('/logout', authenticateToken, <span class="fstat-no" title="function not covered" >(r</span>eq, res) => { | ||||
| <span class="cstat-no" title="statement not covered" >  res.json({ message: 'Logout successful' });</span> | ||||
| }); | ||||
|   | ||||
| module.exports = router; | ||||
|  </pre></td></tr></table></pre> | ||||
|  | ||||
|                 <div class='push'></div><!-- for sticky footer --> | ||||
|             </div><!-- /wrapper --> | ||||
|             <div class='footer quiet pad2 space-top1 center small'> | ||||
|                 Code coverage generated by | ||||
|                 <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> | ||||
|                 at 2025-10-17T03:41:07.369Z | ||||
|             </div> | ||||
|         <script src="../../prettify.js"></script> | ||||
|         <script> | ||||
|             window.onload = function () { | ||||
|                 prettyPrint(); | ||||
|             }; | ||||
|         </script> | ||||
|         <script src="../../sorter.js"></script> | ||||
|         <script src="../../block-navigation.js"></script> | ||||
|     </body> | ||||
| </html> | ||||
|      | ||||
							
								
								
									
										1207
									
								
								backend/coverage/lcov-report/src/routes/candidates.js.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1207
									
								
								backend/coverage/lcov-report/src/routes/candidates.js.html
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										853
									
								
								backend/coverage/lcov-report/src/routes/employers.js.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										853
									
								
								backend/coverage/lcov-report/src/routes/employers.js.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,853 @@ | ||||
|  | ||||
| <!doctype html> | ||||
| <html lang="en"> | ||||
|  | ||||
| <head> | ||||
|     <title>Code coverage report for src/routes/employers.js</title> | ||||
|     <meta charset="utf-8" /> | ||||
|     <link rel="stylesheet" href="../../prettify.css" /> | ||||
|     <link rel="stylesheet" href="../../base.css" /> | ||||
|     <link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" /> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1" /> | ||||
|     <style type='text/css'> | ||||
|         .coverage-summary .sorter { | ||||
|             background-image: url(../../sort-arrow-sprite.png); | ||||
|         } | ||||
|     </style> | ||||
| </head> | ||||
|      | ||||
| <body> | ||||
| <div class='wrapper'> | ||||
|     <div class='pad1'> | ||||
|         <h1><a href="../../index.html">All files</a> / <a href="index.html">src/routes</a> employers.js</h1> | ||||
|         <div class='clearfix'> | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">42.1% </span> | ||||
|                 <span class="quiet">Statements</span> | ||||
|                 <span class='fraction'>48/114</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">38.88% </span> | ||||
|                 <span class="quiet">Branches</span> | ||||
|                 <span class='fraction'>14/36</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">60% </span> | ||||
|                 <span class="quiet">Functions</span> | ||||
|                 <span class='fraction'>3/5</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">42.1% </span> | ||||
|                 <span class="quiet">Lines</span> | ||||
|                 <span class='fraction'>48/114</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|         </div> | ||||
|         <p class="quiet"> | ||||
|             Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block. | ||||
|         </p> | ||||
|         <template id="filterTemplate"> | ||||
|             <div class="quiet"> | ||||
|                 Filter: | ||||
|                 <input type="search" id="fileSearch"> | ||||
|             </div> | ||||
|         </template> | ||||
|     </div> | ||||
|     <div class='status-line low'></div> | ||||
|     <pre><table class="coverage"> | ||||
| <tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a> | ||||
| <a name='L2'></a><a href='#L2'>2</a> | ||||
| <a name='L3'></a><a href='#L3'>3</a> | ||||
| <a name='L4'></a><a href='#L4'>4</a> | ||||
| <a name='L5'></a><a href='#L5'>5</a> | ||||
| <a name='L6'></a><a href='#L6'>6</a> | ||||
| <a name='L7'></a><a href='#L7'>7</a> | ||||
| <a name='L8'></a><a href='#L8'>8</a> | ||||
| <a name='L9'></a><a href='#L9'>9</a> | ||||
| <a name='L10'></a><a href='#L10'>10</a> | ||||
| <a name='L11'></a><a href='#L11'>11</a> | ||||
| <a name='L12'></a><a href='#L12'>12</a> | ||||
| <a name='L13'></a><a href='#L13'>13</a> | ||||
| <a name='L14'></a><a href='#L14'>14</a> | ||||
| <a name='L15'></a><a href='#L15'>15</a> | ||||
| <a name='L16'></a><a href='#L16'>16</a> | ||||
| <a name='L17'></a><a href='#L17'>17</a> | ||||
| <a name='L18'></a><a href='#L18'>18</a> | ||||
| <a name='L19'></a><a href='#L19'>19</a> | ||||
| <a name='L20'></a><a href='#L20'>20</a> | ||||
| <a name='L21'></a><a href='#L21'>21</a> | ||||
| <a name='L22'></a><a href='#L22'>22</a> | ||||
| <a name='L23'></a><a href='#L23'>23</a> | ||||
| <a name='L24'></a><a href='#L24'>24</a> | ||||
| <a name='L25'></a><a href='#L25'>25</a> | ||||
| <a name='L26'></a><a href='#L26'>26</a> | ||||
| <a name='L27'></a><a href='#L27'>27</a> | ||||
| <a name='L28'></a><a href='#L28'>28</a> | ||||
| <a name='L29'></a><a href='#L29'>29</a> | ||||
| <a name='L30'></a><a href='#L30'>30</a> | ||||
| <a name='L31'></a><a href='#L31'>31</a> | ||||
| <a name='L32'></a><a href='#L32'>32</a> | ||||
| <a name='L33'></a><a href='#L33'>33</a> | ||||
| <a name='L34'></a><a href='#L34'>34</a> | ||||
| <a name='L35'></a><a href='#L35'>35</a> | ||||
| <a name='L36'></a><a href='#L36'>36</a> | ||||
| <a name='L37'></a><a href='#L37'>37</a> | ||||
| <a name='L38'></a><a href='#L38'>38</a> | ||||
| <a name='L39'></a><a href='#L39'>39</a> | ||||
| <a name='L40'></a><a href='#L40'>40</a> | ||||
| <a name='L41'></a><a href='#L41'>41</a> | ||||
| <a name='L42'></a><a href='#L42'>42</a> | ||||
| <a name='L43'></a><a href='#L43'>43</a> | ||||
| <a name='L44'></a><a href='#L44'>44</a> | ||||
| <a name='L45'></a><a href='#L45'>45</a> | ||||
| <a name='L46'></a><a href='#L46'>46</a> | ||||
| <a name='L47'></a><a href='#L47'>47</a> | ||||
| <a name='L48'></a><a href='#L48'>48</a> | ||||
| <a name='L49'></a><a href='#L49'>49</a> | ||||
| <a name='L50'></a><a href='#L50'>50</a> | ||||
| <a name='L51'></a><a href='#L51'>51</a> | ||||
| <a name='L52'></a><a href='#L52'>52</a> | ||||
| <a name='L53'></a><a href='#L53'>53</a> | ||||
| <a name='L54'></a><a href='#L54'>54</a> | ||||
| <a name='L55'></a><a href='#L55'>55</a> | ||||
| <a name='L56'></a><a href='#L56'>56</a> | ||||
| <a name='L57'></a><a href='#L57'>57</a> | ||||
| <a name='L58'></a><a href='#L58'>58</a> | ||||
| <a name='L59'></a><a href='#L59'>59</a> | ||||
| <a name='L60'></a><a href='#L60'>60</a> | ||||
| <a name='L61'></a><a href='#L61'>61</a> | ||||
| <a name='L62'></a><a href='#L62'>62</a> | ||||
| <a name='L63'></a><a href='#L63'>63</a> | ||||
| <a name='L64'></a><a href='#L64'>64</a> | ||||
| <a name='L65'></a><a href='#L65'>65</a> | ||||
| <a name='L66'></a><a href='#L66'>66</a> | ||||
| <a name='L67'></a><a href='#L67'>67</a> | ||||
| <a name='L68'></a><a href='#L68'>68</a> | ||||
| <a name='L69'></a><a href='#L69'>69</a> | ||||
| <a name='L70'></a><a href='#L70'>70</a> | ||||
| <a name='L71'></a><a href='#L71'>71</a> | ||||
| <a name='L72'></a><a href='#L72'>72</a> | ||||
| <a name='L73'></a><a href='#L73'>73</a> | ||||
| <a name='L74'></a><a href='#L74'>74</a> | ||||
| <a name='L75'></a><a href='#L75'>75</a> | ||||
| <a name='L76'></a><a href='#L76'>76</a> | ||||
| <a name='L77'></a><a href='#L77'>77</a> | ||||
| <a name='L78'></a><a href='#L78'>78</a> | ||||
| <a name='L79'></a><a href='#L79'>79</a> | ||||
| <a name='L80'></a><a href='#L80'>80</a> | ||||
| <a name='L81'></a><a href='#L81'>81</a> | ||||
| <a name='L82'></a><a href='#L82'>82</a> | ||||
| <a name='L83'></a><a href='#L83'>83</a> | ||||
| <a name='L84'></a><a href='#L84'>84</a> | ||||
| <a name='L85'></a><a href='#L85'>85</a> | ||||
| <a name='L86'></a><a href='#L86'>86</a> | ||||
| <a name='L87'></a><a href='#L87'>87</a> | ||||
| <a name='L88'></a><a href='#L88'>88</a> | ||||
| <a name='L89'></a><a href='#L89'>89</a> | ||||
| <a name='L90'></a><a href='#L90'>90</a> | ||||
| <a name='L91'></a><a href='#L91'>91</a> | ||||
| <a name='L92'></a><a href='#L92'>92</a> | ||||
| <a name='L93'></a><a href='#L93'>93</a> | ||||
| <a name='L94'></a><a href='#L94'>94</a> | ||||
| <a name='L95'></a><a href='#L95'>95</a> | ||||
| <a name='L96'></a><a href='#L96'>96</a> | ||||
| <a name='L97'></a><a href='#L97'>97</a> | ||||
| <a name='L98'></a><a href='#L98'>98</a> | ||||
| <a name='L99'></a><a href='#L99'>99</a> | ||||
| <a name='L100'></a><a href='#L100'>100</a> | ||||
| <a name='L101'></a><a href='#L101'>101</a> | ||||
| <a name='L102'></a><a href='#L102'>102</a> | ||||
| <a name='L103'></a><a href='#L103'>103</a> | ||||
| <a name='L104'></a><a href='#L104'>104</a> | ||||
| <a name='L105'></a><a href='#L105'>105</a> | ||||
| <a name='L106'></a><a href='#L106'>106</a> | ||||
| <a name='L107'></a><a href='#L107'>107</a> | ||||
| <a name='L108'></a><a href='#L108'>108</a> | ||||
| <a name='L109'></a><a href='#L109'>109</a> | ||||
| <a name='L110'></a><a href='#L110'>110</a> | ||||
| <a name='L111'></a><a href='#L111'>111</a> | ||||
| <a name='L112'></a><a href='#L112'>112</a> | ||||
| <a name='L113'></a><a href='#L113'>113</a> | ||||
| <a name='L114'></a><a href='#L114'>114</a> | ||||
| <a name='L115'></a><a href='#L115'>115</a> | ||||
| <a name='L116'></a><a href='#L116'>116</a> | ||||
| <a name='L117'></a><a href='#L117'>117</a> | ||||
| <a name='L118'></a><a href='#L118'>118</a> | ||||
| <a name='L119'></a><a href='#L119'>119</a> | ||||
| <a name='L120'></a><a href='#L120'>120</a> | ||||
| <a name='L121'></a><a href='#L121'>121</a> | ||||
| <a name='L122'></a><a href='#L122'>122</a> | ||||
| <a name='L123'></a><a href='#L123'>123</a> | ||||
| <a name='L124'></a><a href='#L124'>124</a> | ||||
| <a name='L125'></a><a href='#L125'>125</a> | ||||
| <a name='L126'></a><a href='#L126'>126</a> | ||||
| <a name='L127'></a><a href='#L127'>127</a> | ||||
| <a name='L128'></a><a href='#L128'>128</a> | ||||
| <a name='L129'></a><a href='#L129'>129</a> | ||||
| <a name='L130'></a><a href='#L130'>130</a> | ||||
| <a name='L131'></a><a href='#L131'>131</a> | ||||
| <a name='L132'></a><a href='#L132'>132</a> | ||||
| <a name='L133'></a><a href='#L133'>133</a> | ||||
| <a name='L134'></a><a href='#L134'>134</a> | ||||
| <a name='L135'></a><a href='#L135'>135</a> | ||||
| <a name='L136'></a><a href='#L136'>136</a> | ||||
| <a name='L137'></a><a href='#L137'>137</a> | ||||
| <a name='L138'></a><a href='#L138'>138</a> | ||||
| <a name='L139'></a><a href='#L139'>139</a> | ||||
| <a name='L140'></a><a href='#L140'>140</a> | ||||
| <a name='L141'></a><a href='#L141'>141</a> | ||||
| <a name='L142'></a><a href='#L142'>142</a> | ||||
| <a name='L143'></a><a href='#L143'>143</a> | ||||
| <a name='L144'></a><a href='#L144'>144</a> | ||||
| <a name='L145'></a><a href='#L145'>145</a> | ||||
| <a name='L146'></a><a href='#L146'>146</a> | ||||
| <a name='L147'></a><a href='#L147'>147</a> | ||||
| <a name='L148'></a><a href='#L148'>148</a> | ||||
| <a name='L149'></a><a href='#L149'>149</a> | ||||
| <a name='L150'></a><a href='#L150'>150</a> | ||||
| <a name='L151'></a><a href='#L151'>151</a> | ||||
| <a name='L152'></a><a href='#L152'>152</a> | ||||
| <a name='L153'></a><a href='#L153'>153</a> | ||||
| <a name='L154'></a><a href='#L154'>154</a> | ||||
| <a name='L155'></a><a href='#L155'>155</a> | ||||
| <a name='L156'></a><a href='#L156'>156</a> | ||||
| <a name='L157'></a><a href='#L157'>157</a> | ||||
| <a name='L158'></a><a href='#L158'>158</a> | ||||
| <a name='L159'></a><a href='#L159'>159</a> | ||||
| <a name='L160'></a><a href='#L160'>160</a> | ||||
| <a name='L161'></a><a href='#L161'>161</a> | ||||
| <a name='L162'></a><a href='#L162'>162</a> | ||||
| <a name='L163'></a><a href='#L163'>163</a> | ||||
| <a name='L164'></a><a href='#L164'>164</a> | ||||
| <a name='L165'></a><a href='#L165'>165</a> | ||||
| <a name='L166'></a><a href='#L166'>166</a> | ||||
| <a name='L167'></a><a href='#L167'>167</a> | ||||
| <a name='L168'></a><a href='#L168'>168</a> | ||||
| <a name='L169'></a><a href='#L169'>169</a> | ||||
| <a name='L170'></a><a href='#L170'>170</a> | ||||
| <a name='L171'></a><a href='#L171'>171</a> | ||||
| <a name='L172'></a><a href='#L172'>172</a> | ||||
| <a name='L173'></a><a href='#L173'>173</a> | ||||
| <a name='L174'></a><a href='#L174'>174</a> | ||||
| <a name='L175'></a><a href='#L175'>175</a> | ||||
| <a name='L176'></a><a href='#L176'>176</a> | ||||
| <a name='L177'></a><a href='#L177'>177</a> | ||||
| <a name='L178'></a><a href='#L178'>178</a> | ||||
| <a name='L179'></a><a href='#L179'>179</a> | ||||
| <a name='L180'></a><a href='#L180'>180</a> | ||||
| <a name='L181'></a><a href='#L181'>181</a> | ||||
| <a name='L182'></a><a href='#L182'>182</a> | ||||
| <a name='L183'></a><a href='#L183'>183</a> | ||||
| <a name='L184'></a><a href='#L184'>184</a> | ||||
| <a name='L185'></a><a href='#L185'>185</a> | ||||
| <a name='L186'></a><a href='#L186'>186</a> | ||||
| <a name='L187'></a><a href='#L187'>187</a> | ||||
| <a name='L188'></a><a href='#L188'>188</a> | ||||
| <a name='L189'></a><a href='#L189'>189</a> | ||||
| <a name='L190'></a><a href='#L190'>190</a> | ||||
| <a name='L191'></a><a href='#L191'>191</a> | ||||
| <a name='L192'></a><a href='#L192'>192</a> | ||||
| <a name='L193'></a><a href='#L193'>193</a> | ||||
| <a name='L194'></a><a href='#L194'>194</a> | ||||
| <a name='L195'></a><a href='#L195'>195</a> | ||||
| <a name='L196'></a><a href='#L196'>196</a> | ||||
| <a name='L197'></a><a href='#L197'>197</a> | ||||
| <a name='L198'></a><a href='#L198'>198</a> | ||||
| <a name='L199'></a><a href='#L199'>199</a> | ||||
| <a name='L200'></a><a href='#L200'>200</a> | ||||
| <a name='L201'></a><a href='#L201'>201</a> | ||||
| <a name='L202'></a><a href='#L202'>202</a> | ||||
| <a name='L203'></a><a href='#L203'>203</a> | ||||
| <a name='L204'></a><a href='#L204'>204</a> | ||||
| <a name='L205'></a><a href='#L205'>205</a> | ||||
| <a name='L206'></a><a href='#L206'>206</a> | ||||
| <a name='L207'></a><a href='#L207'>207</a> | ||||
| <a name='L208'></a><a href='#L208'>208</a> | ||||
| <a name='L209'></a><a href='#L209'>209</a> | ||||
| <a name='L210'></a><a href='#L210'>210</a> | ||||
| <a name='L211'></a><a href='#L211'>211</a> | ||||
| <a name='L212'></a><a href='#L212'>212</a> | ||||
| <a name='L213'></a><a href='#L213'>213</a> | ||||
| <a name='L214'></a><a href='#L214'>214</a> | ||||
| <a name='L215'></a><a href='#L215'>215</a> | ||||
| <a name='L216'></a><a href='#L216'>216</a> | ||||
| <a name='L217'></a><a href='#L217'>217</a> | ||||
| <a name='L218'></a><a href='#L218'>218</a> | ||||
| <a name='L219'></a><a href='#L219'>219</a> | ||||
| <a name='L220'></a><a href='#L220'>220</a> | ||||
| <a name='L221'></a><a href='#L221'>221</a> | ||||
| <a name='L222'></a><a href='#L222'>222</a> | ||||
| <a name='L223'></a><a href='#L223'>223</a> | ||||
| <a name='L224'></a><a href='#L224'>224</a> | ||||
| <a name='L225'></a><a href='#L225'>225</a> | ||||
| <a name='L226'></a><a href='#L226'>226</a> | ||||
| <a name='L227'></a><a href='#L227'>227</a> | ||||
| <a name='L228'></a><a href='#L228'>228</a> | ||||
| <a name='L229'></a><a href='#L229'>229</a> | ||||
| <a name='L230'></a><a href='#L230'>230</a> | ||||
| <a name='L231'></a><a href='#L231'>231</a> | ||||
| <a name='L232'></a><a href='#L232'>232</a> | ||||
| <a name='L233'></a><a href='#L233'>233</a> | ||||
| <a name='L234'></a><a href='#L234'>234</a> | ||||
| <a name='L235'></a><a href='#L235'>235</a> | ||||
| <a name='L236'></a><a href='#L236'>236</a> | ||||
| <a name='L237'></a><a href='#L237'>237</a> | ||||
| <a name='L238'></a><a href='#L238'>238</a> | ||||
| <a name='L239'></a><a href='#L239'>239</a> | ||||
| <a name='L240'></a><a href='#L240'>240</a> | ||||
| <a name='L241'></a><a href='#L241'>241</a> | ||||
| <a name='L242'></a><a href='#L242'>242</a> | ||||
| <a name='L243'></a><a href='#L243'>243</a> | ||||
| <a name='L244'></a><a href='#L244'>244</a> | ||||
| <a name='L245'></a><a href='#L245'>245</a> | ||||
| <a name='L246'></a><a href='#L246'>246</a> | ||||
| <a name='L247'></a><a href='#L247'>247</a> | ||||
| <a name='L248'></a><a href='#L248'>248</a> | ||||
| <a name='L249'></a><a href='#L249'>249</a> | ||||
| <a name='L250'></a><a href='#L250'>250</a> | ||||
| <a name='L251'></a><a href='#L251'>251</a> | ||||
| <a name='L252'></a><a href='#L252'>252</a> | ||||
| <a name='L253'></a><a href='#L253'>253</a> | ||||
| <a name='L254'></a><a href='#L254'>254</a> | ||||
| <a name='L255'></a><a href='#L255'>255</a> | ||||
| <a name='L256'></a><a href='#L256'>256</a> | ||||
| <a name='L257'></a><a href='#L257'>257</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">11x</span> | ||||
| <span class="cline-any cline-yes">11x</span> | ||||
| <span class="cline-any cline-yes">11x</span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">11x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">11x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">11x</span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">11x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">11x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">const express = require('express'); | ||||
| const { body, validationResult } = require('express-validator'); | ||||
| const pool = require('../database/connection'); | ||||
| const { authenticateToken, requireRole } = require('../middleware/auth'); | ||||
|   | ||||
| const router = express.Router(); | ||||
|   | ||||
| // Get all employers | ||||
| router.get('/', authenticateToken, async (req, res) => { | ||||
|   try { | ||||
|     const result = await pool.query(` | ||||
|       SELECT e.*, u.email, u.first_name, u.last_name  | ||||
|       FROM employers e  | ||||
|       JOIN users u ON e.user_id = u.id  | ||||
|       ORDER BY e.created_at DESC | ||||
|     `); | ||||
|     res.json(result.rows); | ||||
|   } catch (error) { | ||||
| <span class="cstat-no" title="statement not covered" >    console.error('Get employers error:', error);</span> | ||||
| <span class="cstat-no" title="statement not covered" >    res.status(500).json({ error: 'Failed to fetch employers' });</span> | ||||
|   } | ||||
| }); | ||||
|   | ||||
| // Get employer by ID | ||||
| router.get('/:id', authenticateToken, <span class="fstat-no" title="function not covered" >as</span>ync (req, res) => { | ||||
| <span class="cstat-no" title="statement not covered" >  try {</span> | ||||
|     const { id } = <span class="cstat-no" title="statement not covered" >req.params;</span> | ||||
|      | ||||
|     const result = <span class="cstat-no" title="statement not covered" >await pool.query(`</span> | ||||
|       SELECT e.*, u.email, u.first_name, u.last_name  | ||||
|       FROM employers e  | ||||
|       JOIN users u ON e.user_id = u.id  | ||||
|       WHERE e.id = $1 | ||||
|     `, [id]); | ||||
|   | ||||
| <span class="cstat-no" title="statement not covered" >    if (result.rows.length === 0) {</span> | ||||
| <span class="cstat-no" title="statement not covered" >      return res.status(404).json({ error: 'Employer not found' });</span> | ||||
|     } | ||||
|   | ||||
| <span class="cstat-no" title="statement not covered" >    res.json(result.rows[0]);</span> | ||||
|   } catch (error) { | ||||
| <span class="cstat-no" title="statement not covered" >    console.error('Get employer error:', error);</span> | ||||
| <span class="cstat-no" title="statement not covered" >    res.status(500).json({ error: 'Failed to fetch employer' });</span> | ||||
|   } | ||||
| }); | ||||
|   | ||||
| // Create employer profile | ||||
| router.post('/', authenticateToken, requireRole(['employer']), [ | ||||
|   body('companyName').notEmpty().trim(), | ||||
|   body('industry').optional().trim(), | ||||
|   body('companySize').optional().trim(), | ||||
|   body('website').optional().isURL(), | ||||
|   body('description').optional().trim(), | ||||
|   body('address').optional().trim(), | ||||
|   body('phone').optional().trim() | ||||
| ], async (req, res) => { | ||||
|   try { | ||||
|     const errors = validationResult(req); | ||||
|     <span class="missing-if-branch" title="if path not taken" >I</span>if (!errors.isEmpty()) { | ||||
| <span class="cstat-no" title="statement not covered" >      return res.status(400).json({ errors: errors.array() });</span> | ||||
|     } | ||||
|   | ||||
|     const { | ||||
|       companyName, | ||||
|       industry, | ||||
|       companySize, | ||||
|       website, | ||||
|       description, | ||||
|       address, | ||||
|       phone | ||||
|     } = req.body; | ||||
|   | ||||
|     // Check if employer profile already exists for this user | ||||
|     const existingEmployer = await pool.query( | ||||
|       'SELECT id FROM employers WHERE user_id = $1', | ||||
|       [req.user.id] | ||||
|     ); | ||||
|   | ||||
|     <span class="missing-if-branch" title="if path not taken" >I</span>if (existingEmployer.rows.length > 0) { | ||||
| <span class="cstat-no" title="statement not covered" >      return res.status(400).json({ error: 'Employer profile already exists' });</span> | ||||
|     } | ||||
|   | ||||
|     const result = await pool.query(` | ||||
|       INSERT INTO employers (user_id, company_name, industry, company_size, website, description, address, phone) | ||||
|       VALUES ($1, $2, $3, $4, $5, $6, $7, $8) | ||||
|       RETURNING * | ||||
|     `, [req.user.id, companyName, industry, companySize, website, description, address, phone]); | ||||
|   | ||||
|     res.status(201).json({ | ||||
|       message: 'Employer profile created successfully', | ||||
|       employer: result.rows[0] | ||||
|     }); | ||||
|   } catch (error) { | ||||
| <span class="cstat-no" title="statement not covered" >    console.error('Create employer error:', error);</span> | ||||
| <span class="cstat-no" title="statement not covered" >    res.status(500).json({ error: 'Failed to create employer profile' });</span> | ||||
|   } | ||||
| }); | ||||
|   | ||||
| // Update employer profile | ||||
| router.put('/:id', authenticateToken, [ | ||||
|   body('companyName').optional().notEmpty().trim(), | ||||
|   body('industry').optional().trim(), | ||||
|   body('companySize').optional().trim(), | ||||
|   body('website').optional().isURL(), | ||||
|   body('description').optional().trim(), | ||||
|   body('address').optional().trim(), | ||||
|   body('phone').optional().trim() | ||||
| ], async (req, res) => { | ||||
|   try { | ||||
|     const errors = validationResult(req); | ||||
|     <span class="missing-if-branch" title="if path not taken" >I</span>if (!errors.isEmpty()) { | ||||
| <span class="cstat-no" title="statement not covered" >      return res.status(400).json({ errors: errors.array() });</span> | ||||
|     } | ||||
|   | ||||
|     const { id } = req.params; | ||||
|     const { | ||||
|       companyName, | ||||
|       industry, | ||||
|       companySize, | ||||
|       website, | ||||
|       description, | ||||
|       address, | ||||
|       phone | ||||
|     } = req.body; | ||||
|   | ||||
|     // Check if employer exists and user has permission | ||||
|     const employerResult = await pool.query( | ||||
|       'SELECT user_id FROM employers WHERE id = $1', | ||||
|       [id] | ||||
|     ); | ||||
|   | ||||
|     <span class="missing-if-branch" title="if path not taken" >I</span>if (employerResult.rows.length === 0) { | ||||
| <span class="cstat-no" title="statement not covered" >      return res.status(404).json({ error: 'Employer not found' });</span> | ||||
|     } | ||||
|   | ||||
|     // Users can only update their own employer profile unless they're admin | ||||
|     <span class="missing-if-branch" title="if path not taken" >I</span>if (employerResult.rows[0].user_id !== req.user.id && <span class="branch-1 cbranch-no" title="branch not covered" >req.user.role !== 'admin')</span> { | ||||
| <span class="cstat-no" title="statement not covered" >      return res.status(403).json({ error: 'Access denied' });</span> | ||||
|     } | ||||
|   | ||||
|     const updateFields = []; | ||||
|     const updateValues = []; | ||||
|     let paramCount = 1; | ||||
|   | ||||
|     <span class="missing-if-branch" title="if path not taken" >I</span>if (companyName) { | ||||
| <span class="cstat-no" title="statement not covered" >      updateFields.push(`company_name = $${paramCount}`);</span> | ||||
| <span class="cstat-no" title="statement not covered" >      updateValues.push(companyName);</span> | ||||
| <span class="cstat-no" title="statement not covered" >      paramCount++;</span> | ||||
|     } | ||||
|     <span class="missing-if-branch" title="if path not taken" >I</span>if (industry !== undefined) { | ||||
| <span class="cstat-no" title="statement not covered" >      updateFields.push(`industry = $${paramCount}`);</span> | ||||
| <span class="cstat-no" title="statement not covered" >      updateValues.push(industry);</span> | ||||
| <span class="cstat-no" title="statement not covered" >      paramCount++;</span> | ||||
|     } | ||||
|     <span class="missing-if-branch" title="if path not taken" >I</span>if (companySize !== undefined) { | ||||
| <span class="cstat-no" title="statement not covered" >      updateFields.push(`company_size = $${paramCount}`);</span> | ||||
| <span class="cstat-no" title="statement not covered" >      updateValues.push(companySize);</span> | ||||
| <span class="cstat-no" title="statement not covered" >      paramCount++;</span> | ||||
|     } | ||||
|     <span class="missing-if-branch" title="if path not taken" >I</span>if (website !== undefined) { | ||||
| <span class="cstat-no" title="statement not covered" >      updateFields.push(`website = $${paramCount}`);</span> | ||||
| <span class="cstat-no" title="statement not covered" >      updateValues.push(website);</span> | ||||
| <span class="cstat-no" title="statement not covered" >      paramCount++;</span> | ||||
|     } | ||||
|     <span class="missing-if-branch" title="else path not taken" >E</span>if (description !== undefined) { | ||||
|       updateFields.push(`description = $${paramCount}`); | ||||
|       updateValues.push(description); | ||||
|       paramCount++; | ||||
|     } | ||||
|     <span class="missing-if-branch" title="if path not taken" >I</span>if (address !== undefined) { | ||||
| <span class="cstat-no" title="statement not covered" >      updateFields.push(`address = $${paramCount}`);</span> | ||||
| <span class="cstat-no" title="statement not covered" >      updateValues.push(address);</span> | ||||
| <span class="cstat-no" title="statement not covered" >      paramCount++;</span> | ||||
|     } | ||||
|     <span class="missing-if-branch" title="if path not taken" >I</span>if (phone !== undefined) { | ||||
| <span class="cstat-no" title="statement not covered" >      updateFields.push(`phone = $${paramCount}`);</span> | ||||
| <span class="cstat-no" title="statement not covered" >      updateValues.push(phone);</span> | ||||
| <span class="cstat-no" title="statement not covered" >      paramCount++;</span> | ||||
|     } | ||||
|   | ||||
|     <span class="missing-if-branch" title="if path not taken" >I</span>if (updateFields.length === 0) { | ||||
| <span class="cstat-no" title="statement not covered" >      return res.status(400).json({ error: 'No fields to update' });</span> | ||||
|     } | ||||
|   | ||||
|     updateValues.push(id); | ||||
|     const query = `UPDATE employers SET ${updateFields.join(', ')}, updated_at = CURRENT_TIMESTAMP WHERE id = $${paramCount} RETURNING *`; | ||||
|   | ||||
|     const result = await pool.query(query, updateValues); | ||||
|   | ||||
|     res.json({ | ||||
|       message: 'Employer profile updated successfully', | ||||
|       employer: result.rows[0] | ||||
|     }); | ||||
|   } catch (error) { | ||||
| <span class="cstat-no" title="statement not covered" >    console.error('Update employer error:', error);</span> | ||||
| <span class="cstat-no" title="statement not covered" >    res.status(500).json({ error: 'Failed to update employer profile' });</span> | ||||
|   } | ||||
| }); | ||||
|   | ||||
| // Get employer's jobs | ||||
| router.get('/:id/jobs', authenticateToken, <span class="fstat-no" title="function not covered" >as</span>ync (req, res) => { | ||||
| <span class="cstat-no" title="statement not covered" >  try {</span> | ||||
|     const { id } = <span class="cstat-no" title="statement not covered" >req.params;</span> | ||||
|     const { status, page = <span class="branch-0 cbranch-no" title="branch not covered" >1,</span> limit = <span class="branch-0 cbranch-no" title="branch not covered" >10 </span>} = <span class="cstat-no" title="statement not covered" >req.query;</span> | ||||
|   | ||||
|     let query = <span class="cstat-no" title="statement not covered" >`</span> | ||||
|       SELECT * FROM jobs  | ||||
|       WHERE employer_id = $1 | ||||
|     `; | ||||
|     const queryParams = <span class="cstat-no" title="statement not covered" >[id];</span> | ||||
|     let paramCount = <span class="cstat-no" title="statement not covered" >1;</span> | ||||
|   | ||||
| <span class="cstat-no" title="statement not covered" >    if (status) {</span> | ||||
| <span class="cstat-no" title="statement not covered" >      paramCount++;</span> | ||||
| <span class="cstat-no" title="statement not covered" >      query += ` AND status = $${paramCount}`;</span> | ||||
| <span class="cstat-no" title="statement not covered" >      queryParams.push(status);</span> | ||||
|     } | ||||
|   | ||||
| <span class="cstat-no" title="statement not covered" >    query += ` ORDER BY created_at DESC`;</span> | ||||
|   | ||||
|     // Add pagination | ||||
|     const offset = <span class="cstat-no" title="statement not covered" >(page - 1) * limit;</span> | ||||
| <span class="cstat-no" title="statement not covered" >    paramCount++;</span> | ||||
| <span class="cstat-no" title="statement not covered" >    query += ` LIMIT $${paramCount}`;</span> | ||||
| <span class="cstat-no" title="statement not covered" >    queryParams.push(limit);</span> | ||||
| <span class="cstat-no" title="statement not covered" >    paramCount++;</span> | ||||
| <span class="cstat-no" title="statement not covered" >    query += ` OFFSET $${paramCount}`;</span> | ||||
| <span class="cstat-no" title="statement not covered" >    queryParams.push(offset);</span> | ||||
|   | ||||
|     const result = <span class="cstat-no" title="statement not covered" >await pool.query(query, queryParams);</span> | ||||
|   | ||||
|     // Get total count for pagination | ||||
|     let countQuery = <span class="cstat-no" title="statement not covered" >'SELECT COUNT(*) FROM jobs WHERE employer_id = $1';</span> | ||||
|     const countParams = <span class="cstat-no" title="statement not covered" >[id];</span> | ||||
| <span class="cstat-no" title="statement not covered" >    if (status) {</span> | ||||
| <span class="cstat-no" title="statement not covered" >      countQuery += ' AND status = $2';</span> | ||||
| <span class="cstat-no" title="statement not covered" >      countParams.push(status);</span> | ||||
|     } | ||||
|     const countResult = <span class="cstat-no" title="statement not covered" >await pool.query(countQuery, countParams);</span> | ||||
|   | ||||
| <span class="cstat-no" title="statement not covered" >    res.json({</span> | ||||
|       jobs: result.rows, | ||||
|       pagination: { | ||||
|         page: parseInt(page), | ||||
|         limit: parseInt(limit), | ||||
|         total: parseInt(countResult.rows[0].count), | ||||
|         pages: Math.ceil(countResult.rows[0].count / limit) | ||||
|       } | ||||
|     }); | ||||
|   } catch (error) { | ||||
| <span class="cstat-no" title="statement not covered" >    console.error('Get employer jobs error:', error);</span> | ||||
| <span class="cstat-no" title="statement not covered" >    res.status(500).json({ error: 'Failed to fetch employer jobs' });</span> | ||||
|   } | ||||
| }); | ||||
|   | ||||
| module.exports = router; | ||||
|  </pre></td></tr></table></pre> | ||||
|  | ||||
|                 <div class='push'></div><!-- for sticky footer --> | ||||
|             </div><!-- /wrapper --> | ||||
|             <div class='footer quiet pad2 space-top1 center small'> | ||||
|                 Code coverage generated by | ||||
|                 <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> | ||||
|                 at 2025-10-17T03:41:07.369Z | ||||
|             </div> | ||||
|         <script src="../../prettify.js"></script> | ||||
|         <script> | ||||
|             window.onload = function () { | ||||
|                 prettyPrint(); | ||||
|             }; | ||||
|         </script> | ||||
|         <script src="../../sorter.js"></script> | ||||
|         <script src="../../block-navigation.js"></script> | ||||
|     </body> | ||||
| </html> | ||||
|      | ||||
							
								
								
									
										206
									
								
								backend/coverage/lcov-report/src/routes/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										206
									
								
								backend/coverage/lcov-report/src/routes/index.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,206 @@ | ||||
|  | ||||
| <!doctype html> | ||||
| <html lang="en"> | ||||
|  | ||||
| <head> | ||||
|     <title>Code coverage report for src/routes</title> | ||||
|     <meta charset="utf-8" /> | ||||
|     <link rel="stylesheet" href="../../prettify.css" /> | ||||
|     <link rel="stylesheet" href="../../base.css" /> | ||||
|     <link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" /> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1" /> | ||||
|     <style type='text/css'> | ||||
|         .coverage-summary .sorter { | ||||
|             background-image: url(../../sort-arrow-sprite.png); | ||||
|         } | ||||
|     </style> | ||||
| </head> | ||||
|      | ||||
| <body> | ||||
| <div class='wrapper'> | ||||
|     <div class='pad1'> | ||||
|         <h1><a href="../../index.html">All files</a> src/routes</h1> | ||||
|         <div class='clearfix'> | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">58.38% </span> | ||||
|                 <span class="quiet">Statements</span> | ||||
|                 <span class='fraction'>550/942</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">45.12% </span> | ||||
|                 <span class="quiet">Branches</span> | ||||
|                 <span class='fraction'>176/390</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">80.95% </span> | ||||
|                 <span class="quiet">Functions</span> | ||||
|                 <span class='fraction'>34/42</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">58.42% </span> | ||||
|                 <span class="quiet">Lines</span> | ||||
|                 <span class='fraction'>548/938</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|         </div> | ||||
|         <p class="quiet"> | ||||
|             Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block. | ||||
|         </p> | ||||
|         <template id="filterTemplate"> | ||||
|             <div class="quiet"> | ||||
|                 Filter: | ||||
|                 <input type="search" id="fileSearch"> | ||||
|             </div> | ||||
|         </template> | ||||
|     </div> | ||||
|     <div class='status-line medium'></div> | ||||
|     <div class="pad1"> | ||||
| <table class="coverage-summary"> | ||||
| <thead> | ||||
| <tr> | ||||
|    <th data-col="file" data-fmt="html" data-html="true" class="file">File</th> | ||||
|    <th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th> | ||||
|    <th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th> | ||||
|    <th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th> | ||||
|    <th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th> | ||||
|    <th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th> | ||||
|    <th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th> | ||||
|    <th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th> | ||||
|    <th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th> | ||||
|    <th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th> | ||||
| </tr> | ||||
| </thead> | ||||
| <tbody><tr> | ||||
| 	<td class="file medium" data-value="applications.js"><a href="applications.js.html">applications.js</a></td> | ||||
| 	<td data-value="59.55" class="pic medium"> | ||||
| 	<div class="chart"><div class="cover-fill" style="width: 59%"></div><div class="cover-empty" style="width: 41%"></div></div> | ||||
| 	</td> | ||||
| 	<td data-value="59.55" class="pct medium">59.55%</td> | ||||
| 	<td data-value="178" class="abs medium">106/178</td> | ||||
| 	<td data-value="38.54" class="pct low">38.54%</td> | ||||
| 	<td data-value="96" class="abs low">37/96</td> | ||||
| 	<td data-value="100" class="pct high">100%</td> | ||||
| 	<td data-value="5" class="abs high">5/5</td> | ||||
| 	<td data-value="59.55" class="pct medium">59.55%</td> | ||||
| 	<td data-value="178" class="abs medium">106/178</td> | ||||
| 	</tr> | ||||
|  | ||||
| <tr> | ||||
| 	<td class="file high" data-value="auth.js"><a href="auth.js.html">auth.js</a></td> | ||||
| 	<td data-value="82.35" class="pic high"> | ||||
| 	<div class="chart"><div class="cover-fill" style="width: 82%"></div><div class="cover-empty" style="width: 18%"></div></div> | ||||
| 	</td> | ||||
| 	<td data-value="82.35" class="pct high">82.35%</td> | ||||
| 	<td data-value="51" class="abs high">42/51</td> | ||||
| 	<td data-value="83.33" class="pct high">83.33%</td> | ||||
| 	<td data-value="12" class="abs high">10/12</td> | ||||
| 	<td data-value="75" class="pct high">75%</td> | ||||
| 	<td data-value="4" class="abs high">3/4</td> | ||||
| 	<td data-value="82.35" class="pct high">82.35%</td> | ||||
| 	<td data-value="51" class="abs high">42/51</td> | ||||
| 	</tr> | ||||
|  | ||||
| <tr> | ||||
| 	<td class="file medium" data-value="candidates.js"><a href="candidates.js.html">candidates.js</a></td> | ||||
| 	<td data-value="56.81" class="pic medium"> | ||||
| 	<div class="chart"><div class="cover-fill" style="width: 56%"></div><div class="cover-empty" style="width: 44%"></div></div> | ||||
| 	</td> | ||||
| 	<td data-value="56.81" class="pct medium">56.81%</td> | ||||
| 	<td data-value="176" class="abs medium">100/176</td> | ||||
| 	<td data-value="46.66" class="pct low">46.66%</td> | ||||
| 	<td data-value="60" class="abs low">28/60</td> | ||||
| 	<td data-value="85.71" class="pct high">85.71%</td> | ||||
| 	<td data-value="7" class="abs high">6/7</td> | ||||
| 	<td data-value="56.32" class="pct medium">56.32%</td> | ||||
| 	<td data-value="174" class="abs medium">98/174</td> | ||||
| 	</tr> | ||||
|  | ||||
| <tr> | ||||
| 	<td class="file low" data-value="employers.js"><a href="employers.js.html">employers.js</a></td> | ||||
| 	<td data-value="42.1" class="pic low"> | ||||
| 	<div class="chart"><div class="cover-fill" style="width: 42%"></div><div class="cover-empty" style="width: 58%"></div></div> | ||||
| 	</td> | ||||
| 	<td data-value="42.1" class="pct low">42.1%</td> | ||||
| 	<td data-value="114" class="abs low">48/114</td> | ||||
| 	<td data-value="38.88" class="pct low">38.88%</td> | ||||
| 	<td data-value="36" class="abs low">14/36</td> | ||||
| 	<td data-value="60" class="pct high">60%</td> | ||||
| 	<td data-value="5" class="abs high">3/5</td> | ||||
| 	<td data-value="42.1" class="pct low">42.1%</td> | ||||
| 	<td data-value="114" class="abs low">48/114</td> | ||||
| 	</tr> | ||||
|  | ||||
| <tr> | ||||
| 	<td class="file medium" data-value="jobs.js"><a href="jobs.js.html">jobs.js</a></td> | ||||
| 	<td data-value="58.07" class="pic medium"> | ||||
| 	<div class="chart"><div class="cover-fill" style="width: 58%"></div><div class="cover-empty" style="width: 42%"></div></div> | ||||
| 	</td> | ||||
| 	<td data-value="58.07" class="pct medium">58.07%</td> | ||||
| 	<td data-value="229" class="abs medium">133/229</td> | ||||
| 	<td data-value="57.73" class="pct high">57.73%</td> | ||||
| 	<td data-value="97" class="abs high">56/97</td> | ||||
| 	<td data-value="71.42" class="pct high">71.42%</td> | ||||
| 	<td data-value="7" class="abs high">5/7</td> | ||||
| 	<td data-value="58.59" class="pct medium">58.59%</td> | ||||
| 	<td data-value="227" class="abs medium">133/227</td> | ||||
| 	</tr> | ||||
|  | ||||
| <tr> | ||||
| 	<td class="file high" data-value="resumes.js"><a href="resumes.js.html">resumes.js</a></td> | ||||
| 	<td data-value="64.95" class="pic high"> | ||||
| 	<div class="chart"><div class="cover-fill" style="width: 64%"></div><div class="cover-empty" style="width: 36%"></div></div> | ||||
| 	</td> | ||||
| 	<td data-value="64.95" class="pct high">64.95%</td> | ||||
| 	<td data-value="117" class="abs high">76/117</td> | ||||
| 	<td data-value="34.42" class="pct low">34.42%</td> | ||||
| 	<td data-value="61" class="abs low">21/61</td> | ||||
| 	<td data-value="88.88" class="pct high">88.88%</td> | ||||
| 	<td data-value="9" class="abs high">8/9</td> | ||||
| 	<td data-value="64.95" class="pct high">64.95%</td> | ||||
| 	<td data-value="117" class="abs high">76/117</td> | ||||
| 	</tr> | ||||
|  | ||||
| <tr> | ||||
| 	<td class="file medium" data-value="users.js"><a href="users.js.html">users.js</a></td> | ||||
| 	<td data-value="58.44" class="pic medium"> | ||||
| 	<div class="chart"><div class="cover-fill" style="width: 58%"></div><div class="cover-empty" style="width: 42%"></div></div> | ||||
| 	</td> | ||||
| 	<td data-value="58.44" class="pct medium">58.44%</td> | ||||
| 	<td data-value="77" class="abs medium">45/77</td> | ||||
| 	<td data-value="35.71" class="pct low">35.71%</td> | ||||
| 	<td data-value="28" class="abs low">10/28</td> | ||||
| 	<td data-value="80" class="pct high">80%</td> | ||||
| 	<td data-value="5" class="abs high">4/5</td> | ||||
| 	<td data-value="58.44" class="pct medium">58.44%</td> | ||||
| 	<td data-value="77" class="abs medium">45/77</td> | ||||
| 	</tr> | ||||
|  | ||||
| </tbody> | ||||
| </table> | ||||
| </div> | ||||
|                 <div class='push'></div><!-- for sticky footer --> | ||||
|             </div><!-- /wrapper --> | ||||
|             <div class='footer quiet pad2 space-top1 center small'> | ||||
|                 Code coverage generated by | ||||
|                 <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> | ||||
|                 at 2025-10-17T03:41:07.369Z | ||||
|             </div> | ||||
|         <script src="../../prettify.js"></script> | ||||
|         <script> | ||||
|             window.onload = function () { | ||||
|                 prettyPrint(); | ||||
|             }; | ||||
|         </script> | ||||
|         <script src="../../sorter.js"></script> | ||||
|         <script src="../../block-navigation.js"></script> | ||||
|     </body> | ||||
| </html> | ||||
|      | ||||
							
								
								
									
										1519
									
								
								backend/coverage/lcov-report/src/routes/jobs.js.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1519
									
								
								backend/coverage/lcov-report/src/routes/jobs.js.html
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										988
									
								
								backend/coverage/lcov-report/src/routes/resumes.js.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										988
									
								
								backend/coverage/lcov-report/src/routes/resumes.js.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,988 @@ | ||||
|  | ||||
| <!doctype html> | ||||
| <html lang="en"> | ||||
|  | ||||
| <head> | ||||
|     <title>Code coverage report for src/routes/resumes.js</title> | ||||
|     <meta charset="utf-8" /> | ||||
|     <link rel="stylesheet" href="../../prettify.css" /> | ||||
|     <link rel="stylesheet" href="../../base.css" /> | ||||
|     <link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" /> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1" /> | ||||
|     <style type='text/css'> | ||||
|         .coverage-summary .sorter { | ||||
|             background-image: url(../../sort-arrow-sprite.png); | ||||
|         } | ||||
|     </style> | ||||
| </head> | ||||
|      | ||||
| <body> | ||||
| <div class='wrapper'> | ||||
|     <div class='pad1'> | ||||
|         <h1><a href="../../index.html">All files</a> / <a href="index.html">src/routes</a> resumes.js</h1> | ||||
|         <div class='clearfix'> | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">64.95% </span> | ||||
|                 <span class="quiet">Statements</span> | ||||
|                 <span class='fraction'>76/117</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">34.42% </span> | ||||
|                 <span class="quiet">Branches</span> | ||||
|                 <span class='fraction'>21/61</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">88.88% </span> | ||||
|                 <span class="quiet">Functions</span> | ||||
|                 <span class='fraction'>8/9</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">64.95% </span> | ||||
|                 <span class="quiet">Lines</span> | ||||
|                 <span class='fraction'>76/117</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|         </div> | ||||
|         <p class="quiet"> | ||||
|             Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block. | ||||
|         </p> | ||||
|         <template id="filterTemplate"> | ||||
|             <div class="quiet"> | ||||
|                 Filter: | ||||
|                 <input type="search" id="fileSearch"> | ||||
|             </div> | ||||
|         </template> | ||||
|     </div> | ||||
|     <div class='status-line high'></div> | ||||
|     <pre><table class="coverage"> | ||||
| <tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a> | ||||
| <a name='L2'></a><a href='#L2'>2</a> | ||||
| <a name='L3'></a><a href='#L3'>3</a> | ||||
| <a name='L4'></a><a href='#L4'>4</a> | ||||
| <a name='L5'></a><a href='#L5'>5</a> | ||||
| <a name='L6'></a><a href='#L6'>6</a> | ||||
| <a name='L7'></a><a href='#L7'>7</a> | ||||
| <a name='L8'></a><a href='#L8'>8</a> | ||||
| <a name='L9'></a><a href='#L9'>9</a> | ||||
| <a name='L10'></a><a href='#L10'>10</a> | ||||
| <a name='L11'></a><a href='#L11'>11</a> | ||||
| <a name='L12'></a><a href='#L12'>12</a> | ||||
| <a name='L13'></a><a href='#L13'>13</a> | ||||
| <a name='L14'></a><a href='#L14'>14</a> | ||||
| <a name='L15'></a><a href='#L15'>15</a> | ||||
| <a name='L16'></a><a href='#L16'>16</a> | ||||
| <a name='L17'></a><a href='#L17'>17</a> | ||||
| <a name='L18'></a><a href='#L18'>18</a> | ||||
| <a name='L19'></a><a href='#L19'>19</a> | ||||
| <a name='L20'></a><a href='#L20'>20</a> | ||||
| <a name='L21'></a><a href='#L21'>21</a> | ||||
| <a name='L22'></a><a href='#L22'>22</a> | ||||
| <a name='L23'></a><a href='#L23'>23</a> | ||||
| <a name='L24'></a><a href='#L24'>24</a> | ||||
| <a name='L25'></a><a href='#L25'>25</a> | ||||
| <a name='L26'></a><a href='#L26'>26</a> | ||||
| <a name='L27'></a><a href='#L27'>27</a> | ||||
| <a name='L28'></a><a href='#L28'>28</a> | ||||
| <a name='L29'></a><a href='#L29'>29</a> | ||||
| <a name='L30'></a><a href='#L30'>30</a> | ||||
| <a name='L31'></a><a href='#L31'>31</a> | ||||
| <a name='L32'></a><a href='#L32'>32</a> | ||||
| <a name='L33'></a><a href='#L33'>33</a> | ||||
| <a name='L34'></a><a href='#L34'>34</a> | ||||
| <a name='L35'></a><a href='#L35'>35</a> | ||||
| <a name='L36'></a><a href='#L36'>36</a> | ||||
| <a name='L37'></a><a href='#L37'>37</a> | ||||
| <a name='L38'></a><a href='#L38'>38</a> | ||||
| <a name='L39'></a><a href='#L39'>39</a> | ||||
| <a name='L40'></a><a href='#L40'>40</a> | ||||
| <a name='L41'></a><a href='#L41'>41</a> | ||||
| <a name='L42'></a><a href='#L42'>42</a> | ||||
| <a name='L43'></a><a href='#L43'>43</a> | ||||
| <a name='L44'></a><a href='#L44'>44</a> | ||||
| <a name='L45'></a><a href='#L45'>45</a> | ||||
| <a name='L46'></a><a href='#L46'>46</a> | ||||
| <a name='L47'></a><a href='#L47'>47</a> | ||||
| <a name='L48'></a><a href='#L48'>48</a> | ||||
| <a name='L49'></a><a href='#L49'>49</a> | ||||
| <a name='L50'></a><a href='#L50'>50</a> | ||||
| <a name='L51'></a><a href='#L51'>51</a> | ||||
| <a name='L52'></a><a href='#L52'>52</a> | ||||
| <a name='L53'></a><a href='#L53'>53</a> | ||||
| <a name='L54'></a><a href='#L54'>54</a> | ||||
| <a name='L55'></a><a href='#L55'>55</a> | ||||
| <a name='L56'></a><a href='#L56'>56</a> | ||||
| <a name='L57'></a><a href='#L57'>57</a> | ||||
| <a name='L58'></a><a href='#L58'>58</a> | ||||
| <a name='L59'></a><a href='#L59'>59</a> | ||||
| <a name='L60'></a><a href='#L60'>60</a> | ||||
| <a name='L61'></a><a href='#L61'>61</a> | ||||
| <a name='L62'></a><a href='#L62'>62</a> | ||||
| <a name='L63'></a><a href='#L63'>63</a> | ||||
| <a name='L64'></a><a href='#L64'>64</a> | ||||
| <a name='L65'></a><a href='#L65'>65</a> | ||||
| <a name='L66'></a><a href='#L66'>66</a> | ||||
| <a name='L67'></a><a href='#L67'>67</a> | ||||
| <a name='L68'></a><a href='#L68'>68</a> | ||||
| <a name='L69'></a><a href='#L69'>69</a> | ||||
| <a name='L70'></a><a href='#L70'>70</a> | ||||
| <a name='L71'></a><a href='#L71'>71</a> | ||||
| <a name='L72'></a><a href='#L72'>72</a> | ||||
| <a name='L73'></a><a href='#L73'>73</a> | ||||
| <a name='L74'></a><a href='#L74'>74</a> | ||||
| <a name='L75'></a><a href='#L75'>75</a> | ||||
| <a name='L76'></a><a href='#L76'>76</a> | ||||
| <a name='L77'></a><a href='#L77'>77</a> | ||||
| <a name='L78'></a><a href='#L78'>78</a> | ||||
| <a name='L79'></a><a href='#L79'>79</a> | ||||
| <a name='L80'></a><a href='#L80'>80</a> | ||||
| <a name='L81'></a><a href='#L81'>81</a> | ||||
| <a name='L82'></a><a href='#L82'>82</a> | ||||
| <a name='L83'></a><a href='#L83'>83</a> | ||||
| <a name='L84'></a><a href='#L84'>84</a> | ||||
| <a name='L85'></a><a href='#L85'>85</a> | ||||
| <a name='L86'></a><a href='#L86'>86</a> | ||||
| <a name='L87'></a><a href='#L87'>87</a> | ||||
| <a name='L88'></a><a href='#L88'>88</a> | ||||
| <a name='L89'></a><a href='#L89'>89</a> | ||||
| <a name='L90'></a><a href='#L90'>90</a> | ||||
| <a name='L91'></a><a href='#L91'>91</a> | ||||
| <a name='L92'></a><a href='#L92'>92</a> | ||||
| <a name='L93'></a><a href='#L93'>93</a> | ||||
| <a name='L94'></a><a href='#L94'>94</a> | ||||
| <a name='L95'></a><a href='#L95'>95</a> | ||||
| <a name='L96'></a><a href='#L96'>96</a> | ||||
| <a name='L97'></a><a href='#L97'>97</a> | ||||
| <a name='L98'></a><a href='#L98'>98</a> | ||||
| <a name='L99'></a><a href='#L99'>99</a> | ||||
| <a name='L100'></a><a href='#L100'>100</a> | ||||
| <a name='L101'></a><a href='#L101'>101</a> | ||||
| <a name='L102'></a><a href='#L102'>102</a> | ||||
| <a name='L103'></a><a href='#L103'>103</a> | ||||
| <a name='L104'></a><a href='#L104'>104</a> | ||||
| <a name='L105'></a><a href='#L105'>105</a> | ||||
| <a name='L106'></a><a href='#L106'>106</a> | ||||
| <a name='L107'></a><a href='#L107'>107</a> | ||||
| <a name='L108'></a><a href='#L108'>108</a> | ||||
| <a name='L109'></a><a href='#L109'>109</a> | ||||
| <a name='L110'></a><a href='#L110'>110</a> | ||||
| <a name='L111'></a><a href='#L111'>111</a> | ||||
| <a name='L112'></a><a href='#L112'>112</a> | ||||
| <a name='L113'></a><a href='#L113'>113</a> | ||||
| <a name='L114'></a><a href='#L114'>114</a> | ||||
| <a name='L115'></a><a href='#L115'>115</a> | ||||
| <a name='L116'></a><a href='#L116'>116</a> | ||||
| <a name='L117'></a><a href='#L117'>117</a> | ||||
| <a name='L118'></a><a href='#L118'>118</a> | ||||
| <a name='L119'></a><a href='#L119'>119</a> | ||||
| <a name='L120'></a><a href='#L120'>120</a> | ||||
| <a name='L121'></a><a href='#L121'>121</a> | ||||
| <a name='L122'></a><a href='#L122'>122</a> | ||||
| <a name='L123'></a><a href='#L123'>123</a> | ||||
| <a name='L124'></a><a href='#L124'>124</a> | ||||
| <a name='L125'></a><a href='#L125'>125</a> | ||||
| <a name='L126'></a><a href='#L126'>126</a> | ||||
| <a name='L127'></a><a href='#L127'>127</a> | ||||
| <a name='L128'></a><a href='#L128'>128</a> | ||||
| <a name='L129'></a><a href='#L129'>129</a> | ||||
| <a name='L130'></a><a href='#L130'>130</a> | ||||
| <a name='L131'></a><a href='#L131'>131</a> | ||||
| <a name='L132'></a><a href='#L132'>132</a> | ||||
| <a name='L133'></a><a href='#L133'>133</a> | ||||
| <a name='L134'></a><a href='#L134'>134</a> | ||||
| <a name='L135'></a><a href='#L135'>135</a> | ||||
| <a name='L136'></a><a href='#L136'>136</a> | ||||
| <a name='L137'></a><a href='#L137'>137</a> | ||||
| <a name='L138'></a><a href='#L138'>138</a> | ||||
| <a name='L139'></a><a href='#L139'>139</a> | ||||
| <a name='L140'></a><a href='#L140'>140</a> | ||||
| <a name='L141'></a><a href='#L141'>141</a> | ||||
| <a name='L142'></a><a href='#L142'>142</a> | ||||
| <a name='L143'></a><a href='#L143'>143</a> | ||||
| <a name='L144'></a><a href='#L144'>144</a> | ||||
| <a name='L145'></a><a href='#L145'>145</a> | ||||
| <a name='L146'></a><a href='#L146'>146</a> | ||||
| <a name='L147'></a><a href='#L147'>147</a> | ||||
| <a name='L148'></a><a href='#L148'>148</a> | ||||
| <a name='L149'></a><a href='#L149'>149</a> | ||||
| <a name='L150'></a><a href='#L150'>150</a> | ||||
| <a name='L151'></a><a href='#L151'>151</a> | ||||
| <a name='L152'></a><a href='#L152'>152</a> | ||||
| <a name='L153'></a><a href='#L153'>153</a> | ||||
| <a name='L154'></a><a href='#L154'>154</a> | ||||
| <a name='L155'></a><a href='#L155'>155</a> | ||||
| <a name='L156'></a><a href='#L156'>156</a> | ||||
| <a name='L157'></a><a href='#L157'>157</a> | ||||
| <a name='L158'></a><a href='#L158'>158</a> | ||||
| <a name='L159'></a><a href='#L159'>159</a> | ||||
| <a name='L160'></a><a href='#L160'>160</a> | ||||
| <a name='L161'></a><a href='#L161'>161</a> | ||||
| <a name='L162'></a><a href='#L162'>162</a> | ||||
| <a name='L163'></a><a href='#L163'>163</a> | ||||
| <a name='L164'></a><a href='#L164'>164</a> | ||||
| <a name='L165'></a><a href='#L165'>165</a> | ||||
| <a name='L166'></a><a href='#L166'>166</a> | ||||
| <a name='L167'></a><a href='#L167'>167</a> | ||||
| <a name='L168'></a><a href='#L168'>168</a> | ||||
| <a name='L169'></a><a href='#L169'>169</a> | ||||
| <a name='L170'></a><a href='#L170'>170</a> | ||||
| <a name='L171'></a><a href='#L171'>171</a> | ||||
| <a name='L172'></a><a href='#L172'>172</a> | ||||
| <a name='L173'></a><a href='#L173'>173</a> | ||||
| <a name='L174'></a><a href='#L174'>174</a> | ||||
| <a name='L175'></a><a href='#L175'>175</a> | ||||
| <a name='L176'></a><a href='#L176'>176</a> | ||||
| <a name='L177'></a><a href='#L177'>177</a> | ||||
| <a name='L178'></a><a href='#L178'>178</a> | ||||
| <a name='L179'></a><a href='#L179'>179</a> | ||||
| <a name='L180'></a><a href='#L180'>180</a> | ||||
| <a name='L181'></a><a href='#L181'>181</a> | ||||
| <a name='L182'></a><a href='#L182'>182</a> | ||||
| <a name='L183'></a><a href='#L183'>183</a> | ||||
| <a name='L184'></a><a href='#L184'>184</a> | ||||
| <a name='L185'></a><a href='#L185'>185</a> | ||||
| <a name='L186'></a><a href='#L186'>186</a> | ||||
| <a name='L187'></a><a href='#L187'>187</a> | ||||
| <a name='L188'></a><a href='#L188'>188</a> | ||||
| <a name='L189'></a><a href='#L189'>189</a> | ||||
| <a name='L190'></a><a href='#L190'>190</a> | ||||
| <a name='L191'></a><a href='#L191'>191</a> | ||||
| <a name='L192'></a><a href='#L192'>192</a> | ||||
| <a name='L193'></a><a href='#L193'>193</a> | ||||
| <a name='L194'></a><a href='#L194'>194</a> | ||||
| <a name='L195'></a><a href='#L195'>195</a> | ||||
| <a name='L196'></a><a href='#L196'>196</a> | ||||
| <a name='L197'></a><a href='#L197'>197</a> | ||||
| <a name='L198'></a><a href='#L198'>198</a> | ||||
| <a name='L199'></a><a href='#L199'>199</a> | ||||
| <a name='L200'></a><a href='#L200'>200</a> | ||||
| <a name='L201'></a><a href='#L201'>201</a> | ||||
| <a name='L202'></a><a href='#L202'>202</a> | ||||
| <a name='L203'></a><a href='#L203'>203</a> | ||||
| <a name='L204'></a><a href='#L204'>204</a> | ||||
| <a name='L205'></a><a href='#L205'>205</a> | ||||
| <a name='L206'></a><a href='#L206'>206</a> | ||||
| <a name='L207'></a><a href='#L207'>207</a> | ||||
| <a name='L208'></a><a href='#L208'>208</a> | ||||
| <a name='L209'></a><a href='#L209'>209</a> | ||||
| <a name='L210'></a><a href='#L210'>210</a> | ||||
| <a name='L211'></a><a href='#L211'>211</a> | ||||
| <a name='L212'></a><a href='#L212'>212</a> | ||||
| <a name='L213'></a><a href='#L213'>213</a> | ||||
| <a name='L214'></a><a href='#L214'>214</a> | ||||
| <a name='L215'></a><a href='#L215'>215</a> | ||||
| <a name='L216'></a><a href='#L216'>216</a> | ||||
| <a name='L217'></a><a href='#L217'>217</a> | ||||
| <a name='L218'></a><a href='#L218'>218</a> | ||||
| <a name='L219'></a><a href='#L219'>219</a> | ||||
| <a name='L220'></a><a href='#L220'>220</a> | ||||
| <a name='L221'></a><a href='#L221'>221</a> | ||||
| <a name='L222'></a><a href='#L222'>222</a> | ||||
| <a name='L223'></a><a href='#L223'>223</a> | ||||
| <a name='L224'></a><a href='#L224'>224</a> | ||||
| <a name='L225'></a><a href='#L225'>225</a> | ||||
| <a name='L226'></a><a href='#L226'>226</a> | ||||
| <a name='L227'></a><a href='#L227'>227</a> | ||||
| <a name='L228'></a><a href='#L228'>228</a> | ||||
| <a name='L229'></a><a href='#L229'>229</a> | ||||
| <a name='L230'></a><a href='#L230'>230</a> | ||||
| <a name='L231'></a><a href='#L231'>231</a> | ||||
| <a name='L232'></a><a href='#L232'>232</a> | ||||
| <a name='L233'></a><a href='#L233'>233</a> | ||||
| <a name='L234'></a><a href='#L234'>234</a> | ||||
| <a name='L235'></a><a href='#L235'>235</a> | ||||
| <a name='L236'></a><a href='#L236'>236</a> | ||||
| <a name='L237'></a><a href='#L237'>237</a> | ||||
| <a name='L238'></a><a href='#L238'>238</a> | ||||
| <a name='L239'></a><a href='#L239'>239</a> | ||||
| <a name='L240'></a><a href='#L240'>240</a> | ||||
| <a name='L241'></a><a href='#L241'>241</a> | ||||
| <a name='L242'></a><a href='#L242'>242</a> | ||||
| <a name='L243'></a><a href='#L243'>243</a> | ||||
| <a name='L244'></a><a href='#L244'>244</a> | ||||
| <a name='L245'></a><a href='#L245'>245</a> | ||||
| <a name='L246'></a><a href='#L246'>246</a> | ||||
| <a name='L247'></a><a href='#L247'>247</a> | ||||
| <a name='L248'></a><a href='#L248'>248</a> | ||||
| <a name='L249'></a><a href='#L249'>249</a> | ||||
| <a name='L250'></a><a href='#L250'>250</a> | ||||
| <a name='L251'></a><a href='#L251'>251</a> | ||||
| <a name='L252'></a><a href='#L252'>252</a> | ||||
| <a name='L253'></a><a href='#L253'>253</a> | ||||
| <a name='L254'></a><a href='#L254'>254</a> | ||||
| <a name='L255'></a><a href='#L255'>255</a> | ||||
| <a name='L256'></a><a href='#L256'>256</a> | ||||
| <a name='L257'></a><a href='#L257'>257</a> | ||||
| <a name='L258'></a><a href='#L258'>258</a> | ||||
| <a name='L259'></a><a href='#L259'>259</a> | ||||
| <a name='L260'></a><a href='#L260'>260</a> | ||||
| <a name='L261'></a><a href='#L261'>261</a> | ||||
| <a name='L262'></a><a href='#L262'>262</a> | ||||
| <a name='L263'></a><a href='#L263'>263</a> | ||||
| <a name='L264'></a><a href='#L264'>264</a> | ||||
| <a name='L265'></a><a href='#L265'>265</a> | ||||
| <a name='L266'></a><a href='#L266'>266</a> | ||||
| <a name='L267'></a><a href='#L267'>267</a> | ||||
| <a name='L268'></a><a href='#L268'>268</a> | ||||
| <a name='L269'></a><a href='#L269'>269</a> | ||||
| <a name='L270'></a><a href='#L270'>270</a> | ||||
| <a name='L271'></a><a href='#L271'>271</a> | ||||
| <a name='L272'></a><a href='#L272'>272</a> | ||||
| <a name='L273'></a><a href='#L273'>273</a> | ||||
| <a name='L274'></a><a href='#L274'>274</a> | ||||
| <a name='L275'></a><a href='#L275'>275</a> | ||||
| <a name='L276'></a><a href='#L276'>276</a> | ||||
| <a name='L277'></a><a href='#L277'>277</a> | ||||
| <a name='L278'></a><a href='#L278'>278</a> | ||||
| <a name='L279'></a><a href='#L279'>279</a> | ||||
| <a name='L280'></a><a href='#L280'>280</a> | ||||
| <a name='L281'></a><a href='#L281'>281</a> | ||||
| <a name='L282'></a><a href='#L282'>282</a> | ||||
| <a name='L283'></a><a href='#L283'>283</a> | ||||
| <a name='L284'></a><a href='#L284'>284</a> | ||||
| <a name='L285'></a><a href='#L285'>285</a> | ||||
| <a name='L286'></a><a href='#L286'>286</a> | ||||
| <a name='L287'></a><a href='#L287'>287</a> | ||||
| <a name='L288'></a><a href='#L288'>288</a> | ||||
| <a name='L289'></a><a href='#L289'>289</a> | ||||
| <a name='L290'></a><a href='#L290'>290</a> | ||||
| <a name='L291'></a><a href='#L291'>291</a> | ||||
| <a name='L292'></a><a href='#L292'>292</a> | ||||
| <a name='L293'></a><a href='#L293'>293</a> | ||||
| <a name='L294'></a><a href='#L294'>294</a> | ||||
| <a name='L295'></a><a href='#L295'>295</a> | ||||
| <a name='L296'></a><a href='#L296'>296</a> | ||||
| <a name='L297'></a><a href='#L297'>297</a> | ||||
| <a name='L298'></a><a href='#L298'>298</a> | ||||
| <a name='L299'></a><a href='#L299'>299</a> | ||||
| <a name='L300'></a><a href='#L300'>300</a> | ||||
| <a name='L301'></a><a href='#L301'>301</a> | ||||
| <a name='L302'></a><a href='#L302'>302</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">const express = require('express'); | ||||
| const multer = require('multer'); | ||||
| const path = require('path'); | ||||
| const fs = require('fs'); | ||||
| const { v4: uuidv4 } = require('uuid'); | ||||
| const sanitize = require('sanitize-filename'); | ||||
| const pool = require('../database/connection'); | ||||
| const { authenticateToken, requireRole } = require('../middleware/auth'); | ||||
| const config = require('../config'); | ||||
|   | ||||
| const router = express.Router(); | ||||
|   | ||||
| // Configure multer for file uploads | ||||
| const storage = multer.diskStorage({ | ||||
|   destination: (req, file, cb) => { | ||||
|     const uploadDir = path.join(__dirname, '..', '..', config.uploadDir); | ||||
|     <span class="missing-if-branch" title="if path not taken" >I</span>if (!fs.existsSync(uploadDir)) { | ||||
| <span class="cstat-no" title="statement not covered" >      fs.mkdirSync(uploadDir, { recursive: true });</span> | ||||
|     } | ||||
|     cb(null, uploadDir); | ||||
|   }, | ||||
|   filename: (req, file, cb) => { | ||||
|     const safeOriginal = sanitize(file.originalname); | ||||
|     const uniqueName = `${uuidv4()}-${safeOriginal || <span class="branch-1 cbranch-no" title="branch not covered" >'resume'}</span>`; | ||||
|     cb(null, uniqueName); | ||||
|   } | ||||
| }); | ||||
|   | ||||
| const upload = multer({ | ||||
|   storage: storage, | ||||
|   limits: { | ||||
|     fileSize: 10 * 1024 * 1024 // 10MB limit | ||||
|   }, | ||||
|   fileFilter: (req, file, cb) => { | ||||
|     const allowedTypes = [ | ||||
|       'application/pdf', | ||||
|       'application/msword', | ||||
|       'application/vnd.openxmlformats-officedocument.wordprocessingml.document', | ||||
|       'text/plain' | ||||
|     ]; | ||||
|      | ||||
|     if (allowedTypes.includes(file.mimetype)) { | ||||
|       cb(null, true); | ||||
|     } else <span class="missing-if-branch" title="else path not taken" >E</span>{ | ||||
| <span class="cstat-no" title="statement not covered" >      cb(new Error('Invalid file type. Only PDF, DOC, DOCX, and TXT files are allowed.'));</span> | ||||
|     } | ||||
|   } | ||||
| }); | ||||
|   | ||||
| // Get all resumes for a candidate | ||||
| router.get('/candidate/:candidateId', authenticateToken, async (req, res) => { | ||||
|   try { | ||||
|     const { candidateId } = req.params; | ||||
|   | ||||
|     // Check permissions | ||||
|     if (req.user.role === 'candidate') { | ||||
|       const candidateResult = await pool.query( | ||||
|         'SELECT id FROM candidates WHERE user_id = $1', | ||||
|         [req.user.id] | ||||
|       ); | ||||
|       <span class="missing-if-branch" title="if path not taken" >I</span>if (candidateResult.rows.length === 0 || candidateResult.rows[0].id !== candidateId) { | ||||
| <span class="cstat-no" title="statement not covered" >        return res.status(403).json({ error: 'Access denied' });</span> | ||||
|       } | ||||
|     } else <span class="cstat-no" title="statement not covered" ><span class="missing-if-branch" title="else path not taken" >E</span>if (req.user.role !== 'admin' && req.user.role !== 'recruiter' && req.user.role !== 'employer') {</span> | ||||
| <span class="cstat-no" title="statement not covered" >      return res.status(403).json({ error: 'Access denied' });</span> | ||||
|     } | ||||
|   | ||||
|     const result = await pool.query( | ||||
|       'SELECT * FROM resumes WHERE candidate_id = $1 ORDER BY uploaded_at DESC', | ||||
|       [candidateId] | ||||
|     ); | ||||
|   | ||||
|     res.json(result.rows); | ||||
|   } catch (error) { | ||||
| <span class="cstat-no" title="statement not covered" >    console.error('Get resumes error:', error);</span> | ||||
| <span class="cstat-no" title="statement not covered" >    res.status(500).json({ error: 'Failed to fetch resumes' });</span> | ||||
|   } | ||||
| }); | ||||
|   | ||||
| // Get resume by ID | ||||
| router.get('/:id', authenticateToken, <span class="fstat-no" title="function not covered" >as</span>ync (req, res) => { | ||||
| <span class="cstat-no" title="statement not covered" >  try {</span> | ||||
|     const { id } = <span class="cstat-no" title="statement not covered" >req.params;</span> | ||||
|      | ||||
|     const result = <span class="cstat-no" title="statement not covered" >await pool.query(</span> | ||||
|       'SELECT * FROM resumes WHERE id = $1', | ||||
|       [id] | ||||
|     ); | ||||
|   | ||||
| <span class="cstat-no" title="statement not covered" >    if (result.rows.length === 0) {</span> | ||||
| <span class="cstat-no" title="statement not covered" >      return res.status(404).json({ error: 'Resume not found' });</span> | ||||
|     } | ||||
|   | ||||
|     const resume = <span class="cstat-no" title="statement not covered" >result.rows[0];</span> | ||||
|   | ||||
|     // Check permissions | ||||
| <span class="cstat-no" title="statement not covered" >    if (req.user.role === 'candidate') {</span> | ||||
|       const candidateResult = <span class="cstat-no" title="statement not covered" >await pool.query(</span> | ||||
|         'SELECT id FROM candidates WHERE user_id = $1', | ||||
|         [req.user.id] | ||||
|       ); | ||||
| <span class="cstat-no" title="statement not covered" >      if (candidateResult.rows.length === 0 || candidateResult.rows[0].id !== resume.candidate_id) {</span> | ||||
| <span class="cstat-no" title="statement not covered" >        return res.status(403).json({ error: 'Access denied' });</span> | ||||
|       } | ||||
|     } else <span class="cstat-no" title="statement not covered" >if (req.user.role !== 'admin' && req.user.role !== 'recruiter' && req.user.role !== 'employer') {</span> | ||||
| <span class="cstat-no" title="statement not covered" >      return res.status(403).json({ error: 'Access denied' });</span> | ||||
|     } | ||||
|   | ||||
| <span class="cstat-no" title="statement not covered" >    res.json(resume);</span> | ||||
|   } catch (error) { | ||||
| <span class="cstat-no" title="statement not covered" >    console.error('Get resume error:', error);</span> | ||||
| <span class="cstat-no" title="statement not covered" >    res.status(500).json({ error: 'Failed to fetch resume' });</span> | ||||
|   } | ||||
| }); | ||||
|   | ||||
| // Upload resume | ||||
| router.post('/upload', authenticateToken, requireRole(['candidate']), upload.single('resume'), async (req, res) => { | ||||
|   try { | ||||
|     <span class="missing-if-branch" title="if path not taken" >I</span>if (!req.file) { | ||||
| <span class="cstat-no" title="statement not covered" >      return res.status(400).json({ error: 'No file uploaded' });</span> | ||||
|     } | ||||
|   | ||||
|     // Get candidate ID for the current user | ||||
|     const candidateResult = await pool.query( | ||||
|       'SELECT id FROM candidates WHERE user_id = $1', | ||||
|       [req.user.id] | ||||
|     ); | ||||
|   | ||||
|     <span class="missing-if-branch" title="if path not taken" >I</span>if (candidateResult.rows.length === 0) { | ||||
| <span class="cstat-no" title="statement not covered" >      return res.status(400).json({ error: 'Candidate profile not found' });</span> | ||||
|     } | ||||
|   | ||||
|     const candidateId = candidateResult.rows[0].id; | ||||
|   | ||||
|     // If this is set as primary, unset other primary resumes | ||||
|     <span class="missing-if-branch" title="else path not taken" >E</span>if (req.body.isPrimary === 'true') { | ||||
|       await pool.query( | ||||
|         'UPDATE resumes SET is_primary = false WHERE candidate_id = $1', | ||||
|         [candidateId] | ||||
|       ); | ||||
|     } | ||||
|   | ||||
|     const result = await pool.query(` | ||||
|       INSERT INTO resumes (candidate_id, filename, original_name, file_path, file_size, mime_type, is_primary) | ||||
|       VALUES ($1, $2, $3, $4, $5, $6, $7) | ||||
|       RETURNING * | ||||
|     `, [ | ||||
|       candidateId, | ||||
|       req.file.filename, | ||||
|       req.file.originalname, | ||||
|       req.file.path, | ||||
|       req.file.size, | ||||
|       req.file.mimetype, | ||||
|       req.body.isPrimary === 'true' | ||||
|     ]); | ||||
|   | ||||
|     res.status(201).json({ | ||||
|       message: 'Resume uploaded successfully', | ||||
|       resume: result.rows[0] | ||||
|     }); | ||||
|   } catch (error) { | ||||
| <span class="cstat-no" title="statement not covered" >    console.error('Upload resume error:', error);</span> | ||||
| <span class="cstat-no" title="statement not covered" >    res.status(500).json({ error: 'Failed to upload resume' });</span> | ||||
|   } | ||||
| }); | ||||
|   | ||||
| // Download resume | ||||
| router.get('/:id/download', authenticateToken, async (req, res) => { | ||||
|   try { | ||||
|     const { id } = req.params; | ||||
|      | ||||
|     const result = await pool.query( | ||||
|       'SELECT * FROM resumes WHERE id = $1', | ||||
|       [id] | ||||
|     ); | ||||
|   | ||||
|     <span class="missing-if-branch" title="if path not taken" >I</span>if (result.rows.length === 0) { | ||||
| <span class="cstat-no" title="statement not covered" >      return res.status(404).json({ error: 'Resume not found' });</span> | ||||
|     } | ||||
|   | ||||
|     const resume = result.rows[0]; | ||||
|   | ||||
|     // Check permissions | ||||
|     if (req.user.role === 'candidate') { | ||||
|       const candidateResult = await pool.query( | ||||
|         'SELECT id FROM candidates WHERE user_id = $1', | ||||
|         [req.user.id] | ||||
|       ); | ||||
|       <span class="missing-if-branch" title="if path not taken" >I</span>if (candidateResult.rows.length === 0 || candidateResult.rows[0].id !== resume.candidate_id) { | ||||
| <span class="cstat-no" title="statement not covered" >        return res.status(403).json({ error: 'Access denied' });</span> | ||||
|       } | ||||
|     } else <span class="cstat-no" title="statement not covered" ><span class="missing-if-branch" title="else path not taken" >E</span>if (req.user.role !== 'admin' && req.user.role !== 'recruiter' && req.user.role !== 'employer') {</span> | ||||
| <span class="cstat-no" title="statement not covered" >      return res.status(403).json({ error: 'Access denied' });</span> | ||||
|     } | ||||
|   | ||||
|     // Check if file exists | ||||
|     <span class="missing-if-branch" title="if path not taken" >I</span>if (!fs.existsSync(resume.file_path)) { | ||||
| <span class="cstat-no" title="statement not covered" >      return res.status(404).json({ error: 'Resume file not found' });</span> | ||||
|     } | ||||
|   | ||||
|     res.download(resume.file_path, resume.original_name); | ||||
|   } catch (error) { | ||||
| <span class="cstat-no" title="statement not covered" >    console.error('Download resume error:', error);</span> | ||||
| <span class="cstat-no" title="statement not covered" >    res.status(500).json({ error: 'Failed to download resume' });</span> | ||||
|   } | ||||
| }); | ||||
|   | ||||
| // Set primary resume | ||||
| router.put('/:id/primary', authenticateToken, requireRole(['candidate']), async (req, res) => { | ||||
|   try { | ||||
|     const { id } = req.params; | ||||
|   | ||||
|     // Get candidate ID for the current user | ||||
|     const candidateResult = await pool.query( | ||||
|       'SELECT id FROM candidates WHERE user_id = $1', | ||||
|       [req.user.id] | ||||
|     ); | ||||
|   | ||||
|     <span class="missing-if-branch" title="if path not taken" >I</span>if (candidateResult.rows.length === 0) { | ||||
| <span class="cstat-no" title="statement not covered" >      return res.status(400).json({ error: 'Candidate profile not found' });</span> | ||||
|     } | ||||
|   | ||||
|     const candidateId = candidateResult.rows[0].id; | ||||
|   | ||||
|     // Check if resume exists and belongs to the candidate | ||||
|     const resumeResult = await pool.query( | ||||
|       'SELECT id FROM resumes WHERE id = $1 AND candidate_id = $2', | ||||
|       [id, candidateId] | ||||
|     ); | ||||
|   | ||||
|     <span class="missing-if-branch" title="if path not taken" >I</span>if (resumeResult.rows.length === 0) { | ||||
| <span class="cstat-no" title="statement not covered" >      return res.status(404).json({ error: 'Resume not found' });</span> | ||||
|     } | ||||
|   | ||||
|     // Unset other primary resumes | ||||
|     await pool.query( | ||||
|       'UPDATE resumes SET is_primary = false WHERE candidate_id = $1', | ||||
|       [candidateId] | ||||
|     ); | ||||
|   | ||||
|     // Set this resume as primary | ||||
|     const result = await pool.query( | ||||
|       'UPDATE resumes SET is_primary = true WHERE id = $1 RETURNING *', | ||||
|       [id] | ||||
|     ); | ||||
|   | ||||
|     res.json({ | ||||
|       message: 'Primary resume updated successfully', | ||||
|       resume: result.rows[0] | ||||
|     }); | ||||
|   } catch (error) { | ||||
| <span class="cstat-no" title="statement not covered" >    console.error('Set primary resume error:', error);</span> | ||||
| <span class="cstat-no" title="statement not covered" >    res.status(500).json({ error: 'Failed to set primary resume' });</span> | ||||
|   } | ||||
| }); | ||||
|   | ||||
| // Delete resume | ||||
| router.delete('/:id', authenticateToken, requireRole(['candidate']), async (req, res) => { | ||||
|   try { | ||||
|     const { id } = req.params; | ||||
|   | ||||
|     // Get candidate ID for the current user | ||||
|     const candidateResult = await pool.query( | ||||
|       'SELECT id FROM candidates WHERE user_id = $1', | ||||
|       [req.user.id] | ||||
|     ); | ||||
|   | ||||
|     <span class="missing-if-branch" title="if path not taken" >I</span>if (candidateResult.rows.length === 0) { | ||||
| <span class="cstat-no" title="statement not covered" >      return res.status(400).json({ error: 'Candidate profile not found' });</span> | ||||
|     } | ||||
|   | ||||
|     const candidateId = candidateResult.rows[0].id; | ||||
|   | ||||
|     // Check if resume exists and belongs to the candidate | ||||
|     const resumeResult = await pool.query( | ||||
|       'SELECT * FROM resumes WHERE id = $1 AND candidate_id = $2', | ||||
|       [id, candidateId] | ||||
|     ); | ||||
|   | ||||
|     <span class="missing-if-branch" title="if path not taken" >I</span>if (resumeResult.rows.length === 0) { | ||||
| <span class="cstat-no" title="statement not covered" >      return res.status(404).json({ error: 'Resume not found' });</span> | ||||
|     } | ||||
|   | ||||
|     const resume = resumeResult.rows[0]; | ||||
|   | ||||
|     // Delete file from filesystem | ||||
|     <span class="missing-if-branch" title="else path not taken" >E</span>if (fs.existsSync(resume.file_path)) { | ||||
|       fs.unlinkSync(resume.file_path); | ||||
|     } | ||||
|   | ||||
|     // Delete from database | ||||
|     await pool.query('DELETE FROM resumes WHERE id = $1', [id]); | ||||
|   | ||||
|     res.json({ message: 'Resume deleted successfully' }); | ||||
|   } catch (error) { | ||||
| <span class="cstat-no" title="statement not covered" >    console.error('Delete resume error:', error);</span> | ||||
| <span class="cstat-no" title="statement not covered" >    res.status(500).json({ error: 'Failed to delete resume' });</span> | ||||
|   } | ||||
| }); | ||||
|   | ||||
| module.exports = router; | ||||
|  </pre></td></tr></table></pre> | ||||
|  | ||||
|                 <div class='push'></div><!-- for sticky footer --> | ||||
|             </div><!-- /wrapper --> | ||||
|             <div class='footer quiet pad2 space-top1 center small'> | ||||
|                 Code coverage generated by | ||||
|                 <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> | ||||
|                 at 2025-10-17T03:41:07.369Z | ||||
|             </div> | ||||
|         <script src="../../prettify.js"></script> | ||||
|         <script> | ||||
|             window.onload = function () { | ||||
|                 prettyPrint(); | ||||
|             }; | ||||
|         </script> | ||||
|         <script src="../../sorter.js"></script> | ||||
|         <script src="../../block-navigation.js"></script> | ||||
|     </body> | ||||
| </html> | ||||
|      | ||||
							
								
								
									
										580
									
								
								backend/coverage/lcov-report/src/routes/users.js.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										580
									
								
								backend/coverage/lcov-report/src/routes/users.js.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,580 @@ | ||||
|  | ||||
| <!doctype html> | ||||
| <html lang="en"> | ||||
|  | ||||
| <head> | ||||
|     <title>Code coverage report for src/routes/users.js</title> | ||||
|     <meta charset="utf-8" /> | ||||
|     <link rel="stylesheet" href="../../prettify.css" /> | ||||
|     <link rel="stylesheet" href="../../base.css" /> | ||||
|     <link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" /> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1" /> | ||||
|     <style type='text/css'> | ||||
|         .coverage-summary .sorter { | ||||
|             background-image: url(../../sort-arrow-sprite.png); | ||||
|         } | ||||
|     </style> | ||||
| </head> | ||||
|      | ||||
| <body> | ||||
| <div class='wrapper'> | ||||
|     <div class='pad1'> | ||||
|         <h1><a href="../../index.html">All files</a> / <a href="index.html">src/routes</a> users.js</h1> | ||||
|         <div class='clearfix'> | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">58.44% </span> | ||||
|                 <span class="quiet">Statements</span> | ||||
|                 <span class='fraction'>45/77</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">35.71% </span> | ||||
|                 <span class="quiet">Branches</span> | ||||
|                 <span class='fraction'>10/28</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">80% </span> | ||||
|                 <span class="quiet">Functions</span> | ||||
|                 <span class='fraction'>4/5</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">58.44% </span> | ||||
|                 <span class="quiet">Lines</span> | ||||
|                 <span class='fraction'>45/77</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|         </div> | ||||
|         <p class="quiet"> | ||||
|             Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block. | ||||
|         </p> | ||||
|         <template id="filterTemplate"> | ||||
|             <div class="quiet"> | ||||
|                 Filter: | ||||
|                 <input type="search" id="fileSearch"> | ||||
|             </div> | ||||
|         </template> | ||||
|     </div> | ||||
|     <div class='status-line medium'></div> | ||||
|     <pre><table class="coverage"> | ||||
| <tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a> | ||||
| <a name='L2'></a><a href='#L2'>2</a> | ||||
| <a name='L3'></a><a href='#L3'>3</a> | ||||
| <a name='L4'></a><a href='#L4'>4</a> | ||||
| <a name='L5'></a><a href='#L5'>5</a> | ||||
| <a name='L6'></a><a href='#L6'>6</a> | ||||
| <a name='L7'></a><a href='#L7'>7</a> | ||||
| <a name='L8'></a><a href='#L8'>8</a> | ||||
| <a name='L9'></a><a href='#L9'>9</a> | ||||
| <a name='L10'></a><a href='#L10'>10</a> | ||||
| <a name='L11'></a><a href='#L11'>11</a> | ||||
| <a name='L12'></a><a href='#L12'>12</a> | ||||
| <a name='L13'></a><a href='#L13'>13</a> | ||||
| <a name='L14'></a><a href='#L14'>14</a> | ||||
| <a name='L15'></a><a href='#L15'>15</a> | ||||
| <a name='L16'></a><a href='#L16'>16</a> | ||||
| <a name='L17'></a><a href='#L17'>17</a> | ||||
| <a name='L18'></a><a href='#L18'>18</a> | ||||
| <a name='L19'></a><a href='#L19'>19</a> | ||||
| <a name='L20'></a><a href='#L20'>20</a> | ||||
| <a name='L21'></a><a href='#L21'>21</a> | ||||
| <a name='L22'></a><a href='#L22'>22</a> | ||||
| <a name='L23'></a><a href='#L23'>23</a> | ||||
| <a name='L24'></a><a href='#L24'>24</a> | ||||
| <a name='L25'></a><a href='#L25'>25</a> | ||||
| <a name='L26'></a><a href='#L26'>26</a> | ||||
| <a name='L27'></a><a href='#L27'>27</a> | ||||
| <a name='L28'></a><a href='#L28'>28</a> | ||||
| <a name='L29'></a><a href='#L29'>29</a> | ||||
| <a name='L30'></a><a href='#L30'>30</a> | ||||
| <a name='L31'></a><a href='#L31'>31</a> | ||||
| <a name='L32'></a><a href='#L32'>32</a> | ||||
| <a name='L33'></a><a href='#L33'>33</a> | ||||
| <a name='L34'></a><a href='#L34'>34</a> | ||||
| <a name='L35'></a><a href='#L35'>35</a> | ||||
| <a name='L36'></a><a href='#L36'>36</a> | ||||
| <a name='L37'></a><a href='#L37'>37</a> | ||||
| <a name='L38'></a><a href='#L38'>38</a> | ||||
| <a name='L39'></a><a href='#L39'>39</a> | ||||
| <a name='L40'></a><a href='#L40'>40</a> | ||||
| <a name='L41'></a><a href='#L41'>41</a> | ||||
| <a name='L42'></a><a href='#L42'>42</a> | ||||
| <a name='L43'></a><a href='#L43'>43</a> | ||||
| <a name='L44'></a><a href='#L44'>44</a> | ||||
| <a name='L45'></a><a href='#L45'>45</a> | ||||
| <a name='L46'></a><a href='#L46'>46</a> | ||||
| <a name='L47'></a><a href='#L47'>47</a> | ||||
| <a name='L48'></a><a href='#L48'>48</a> | ||||
| <a name='L49'></a><a href='#L49'>49</a> | ||||
| <a name='L50'></a><a href='#L50'>50</a> | ||||
| <a name='L51'></a><a href='#L51'>51</a> | ||||
| <a name='L52'></a><a href='#L52'>52</a> | ||||
| <a name='L53'></a><a href='#L53'>53</a> | ||||
| <a name='L54'></a><a href='#L54'>54</a> | ||||
| <a name='L55'></a><a href='#L55'>55</a> | ||||
| <a name='L56'></a><a href='#L56'>56</a> | ||||
| <a name='L57'></a><a href='#L57'>57</a> | ||||
| <a name='L58'></a><a href='#L58'>58</a> | ||||
| <a name='L59'></a><a href='#L59'>59</a> | ||||
| <a name='L60'></a><a href='#L60'>60</a> | ||||
| <a name='L61'></a><a href='#L61'>61</a> | ||||
| <a name='L62'></a><a href='#L62'>62</a> | ||||
| <a name='L63'></a><a href='#L63'>63</a> | ||||
| <a name='L64'></a><a href='#L64'>64</a> | ||||
| <a name='L65'></a><a href='#L65'>65</a> | ||||
| <a name='L66'></a><a href='#L66'>66</a> | ||||
| <a name='L67'></a><a href='#L67'>67</a> | ||||
| <a name='L68'></a><a href='#L68'>68</a> | ||||
| <a name='L69'></a><a href='#L69'>69</a> | ||||
| <a name='L70'></a><a href='#L70'>70</a> | ||||
| <a name='L71'></a><a href='#L71'>71</a> | ||||
| <a name='L72'></a><a href='#L72'>72</a> | ||||
| <a name='L73'></a><a href='#L73'>73</a> | ||||
| <a name='L74'></a><a href='#L74'>74</a> | ||||
| <a name='L75'></a><a href='#L75'>75</a> | ||||
| <a name='L76'></a><a href='#L76'>76</a> | ||||
| <a name='L77'></a><a href='#L77'>77</a> | ||||
| <a name='L78'></a><a href='#L78'>78</a> | ||||
| <a name='L79'></a><a href='#L79'>79</a> | ||||
| <a name='L80'></a><a href='#L80'>80</a> | ||||
| <a name='L81'></a><a href='#L81'>81</a> | ||||
| <a name='L82'></a><a href='#L82'>82</a> | ||||
| <a name='L83'></a><a href='#L83'>83</a> | ||||
| <a name='L84'></a><a href='#L84'>84</a> | ||||
| <a name='L85'></a><a href='#L85'>85</a> | ||||
| <a name='L86'></a><a href='#L86'>86</a> | ||||
| <a name='L87'></a><a href='#L87'>87</a> | ||||
| <a name='L88'></a><a href='#L88'>88</a> | ||||
| <a name='L89'></a><a href='#L89'>89</a> | ||||
| <a name='L90'></a><a href='#L90'>90</a> | ||||
| <a name='L91'></a><a href='#L91'>91</a> | ||||
| <a name='L92'></a><a href='#L92'>92</a> | ||||
| <a name='L93'></a><a href='#L93'>93</a> | ||||
| <a name='L94'></a><a href='#L94'>94</a> | ||||
| <a name='L95'></a><a href='#L95'>95</a> | ||||
| <a name='L96'></a><a href='#L96'>96</a> | ||||
| <a name='L97'></a><a href='#L97'>97</a> | ||||
| <a name='L98'></a><a href='#L98'>98</a> | ||||
| <a name='L99'></a><a href='#L99'>99</a> | ||||
| <a name='L100'></a><a href='#L100'>100</a> | ||||
| <a name='L101'></a><a href='#L101'>101</a> | ||||
| <a name='L102'></a><a href='#L102'>102</a> | ||||
| <a name='L103'></a><a href='#L103'>103</a> | ||||
| <a name='L104'></a><a href='#L104'>104</a> | ||||
| <a name='L105'></a><a href='#L105'>105</a> | ||||
| <a name='L106'></a><a href='#L106'>106</a> | ||||
| <a name='L107'></a><a href='#L107'>107</a> | ||||
| <a name='L108'></a><a href='#L108'>108</a> | ||||
| <a name='L109'></a><a href='#L109'>109</a> | ||||
| <a name='L110'></a><a href='#L110'>110</a> | ||||
| <a name='L111'></a><a href='#L111'>111</a> | ||||
| <a name='L112'></a><a href='#L112'>112</a> | ||||
| <a name='L113'></a><a href='#L113'>113</a> | ||||
| <a name='L114'></a><a href='#L114'>114</a> | ||||
| <a name='L115'></a><a href='#L115'>115</a> | ||||
| <a name='L116'></a><a href='#L116'>116</a> | ||||
| <a name='L117'></a><a href='#L117'>117</a> | ||||
| <a name='L118'></a><a href='#L118'>118</a> | ||||
| <a name='L119'></a><a href='#L119'>119</a> | ||||
| <a name='L120'></a><a href='#L120'>120</a> | ||||
| <a name='L121'></a><a href='#L121'>121</a> | ||||
| <a name='L122'></a><a href='#L122'>122</a> | ||||
| <a name='L123'></a><a href='#L123'>123</a> | ||||
| <a name='L124'></a><a href='#L124'>124</a> | ||||
| <a name='L125'></a><a href='#L125'>125</a> | ||||
| <a name='L126'></a><a href='#L126'>126</a> | ||||
| <a name='L127'></a><a href='#L127'>127</a> | ||||
| <a name='L128'></a><a href='#L128'>128</a> | ||||
| <a name='L129'></a><a href='#L129'>129</a> | ||||
| <a name='L130'></a><a href='#L130'>130</a> | ||||
| <a name='L131'></a><a href='#L131'>131</a> | ||||
| <a name='L132'></a><a href='#L132'>132</a> | ||||
| <a name='L133'></a><a href='#L133'>133</a> | ||||
| <a name='L134'></a><a href='#L134'>134</a> | ||||
| <a name='L135'></a><a href='#L135'>135</a> | ||||
| <a name='L136'></a><a href='#L136'>136</a> | ||||
| <a name='L137'></a><a href='#L137'>137</a> | ||||
| <a name='L138'></a><a href='#L138'>138</a> | ||||
| <a name='L139'></a><a href='#L139'>139</a> | ||||
| <a name='L140'></a><a href='#L140'>140</a> | ||||
| <a name='L141'></a><a href='#L141'>141</a> | ||||
| <a name='L142'></a><a href='#L142'>142</a> | ||||
| <a name='L143'></a><a href='#L143'>143</a> | ||||
| <a name='L144'></a><a href='#L144'>144</a> | ||||
| <a name='L145'></a><a href='#L145'>145</a> | ||||
| <a name='L146'></a><a href='#L146'>146</a> | ||||
| <a name='L147'></a><a href='#L147'>147</a> | ||||
| <a name='L148'></a><a href='#L148'>148</a> | ||||
| <a name='L149'></a><a href='#L149'>149</a> | ||||
| <a name='L150'></a><a href='#L150'>150</a> | ||||
| <a name='L151'></a><a href='#L151'>151</a> | ||||
| <a name='L152'></a><a href='#L152'>152</a> | ||||
| <a name='L153'></a><a href='#L153'>153</a> | ||||
| <a name='L154'></a><a href='#L154'>154</a> | ||||
| <a name='L155'></a><a href='#L155'>155</a> | ||||
| <a name='L156'></a><a href='#L156'>156</a> | ||||
| <a name='L157'></a><a href='#L157'>157</a> | ||||
| <a name='L158'></a><a href='#L158'>158</a> | ||||
| <a name='L159'></a><a href='#L159'>159</a> | ||||
| <a name='L160'></a><a href='#L160'>160</a> | ||||
| <a name='L161'></a><a href='#L161'>161</a> | ||||
| <a name='L162'></a><a href='#L162'>162</a> | ||||
| <a name='L163'></a><a href='#L163'>163</a> | ||||
| <a name='L164'></a><a href='#L164'>164</a> | ||||
| <a name='L165'></a><a href='#L165'>165</a> | ||||
| <a name='L166'></a><a href='#L166'>166</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">const express = require('express'); | ||||
| const { body, validationResult } = require('express-validator'); | ||||
| const pool = require('../database/connection'); | ||||
| const { authenticateToken, requireRole } = require('../middleware/auth'); | ||||
|   | ||||
| const router = express.Router(); | ||||
|   | ||||
| // Get all users (admin only) | ||||
| router.get('/', authenticateToken, requireRole(['admin']), async (req, res) => { | ||||
|   try { | ||||
|     const result = await pool.query( | ||||
|       'SELECT id, email, first_name, last_name, role, is_active, created_at FROM users ORDER BY created_at DESC' | ||||
|     ); | ||||
|     res.json(result.rows); | ||||
|   } catch (error) { | ||||
| <span class="cstat-no" title="statement not covered" >    console.error('Get users error:', error);</span> | ||||
| <span class="cstat-no" title="statement not covered" >    res.status(500).json({ error: 'Failed to fetch users' });</span> | ||||
|   } | ||||
| }); | ||||
|   | ||||
| // Get user by ID | ||||
| router.get('/:id', authenticateToken, <span class="fstat-no" title="function not covered" >as</span>ync (req, res) => { | ||||
| <span class="cstat-no" title="statement not covered" >  try {</span> | ||||
|     const { id } = <span class="cstat-no" title="statement not covered" >req.params;</span> | ||||
|      | ||||
|     // Users can only view their own profile unless they're admin | ||||
| <span class="cstat-no" title="statement not covered" >    if (req.user.id !== id && req.user.role !== 'admin') {</span> | ||||
| <span class="cstat-no" title="statement not covered" >      return res.status(403).json({ error: 'Access denied' });</span> | ||||
|     } | ||||
|   | ||||
|     const result = <span class="cstat-no" title="statement not covered" >await pool.query(</span> | ||||
|       'SELECT id, email, first_name, last_name, role, is_active, created_at FROM users WHERE id = $1', | ||||
|       [id] | ||||
|     ); | ||||
|   | ||||
| <span class="cstat-no" title="statement not covered" >    if (result.rows.length === 0) {</span> | ||||
| <span class="cstat-no" title="statement not covered" >      return res.status(404).json({ error: 'User not found' });</span> | ||||
|     } | ||||
|   | ||||
| <span class="cstat-no" title="statement not covered" >    res.json(result.rows[0]);</span> | ||||
|   } catch (error) { | ||||
| <span class="cstat-no" title="statement not covered" >    console.error('Get user error:', error);</span> | ||||
| <span class="cstat-no" title="statement not covered" >    res.status(500).json({ error: 'Failed to fetch user' });</span> | ||||
|   } | ||||
| }); | ||||
|   | ||||
| // Update user profile | ||||
| router.put('/:id', authenticateToken, [ | ||||
|   body('firstName').optional().notEmpty().trim(), | ||||
|   body('lastName').optional().notEmpty().trim(), | ||||
|   body('email').optional().isEmail().normalizeEmail() | ||||
| ], async (req, res) => { | ||||
|   try { | ||||
|     const errors = validationResult(req); | ||||
|     <span class="missing-if-branch" title="if path not taken" >I</span>if (!errors.isEmpty()) { | ||||
| <span class="cstat-no" title="statement not covered" >      return res.status(400).json({ errors: errors.array() });</span> | ||||
|     } | ||||
|   | ||||
|     const { id } = req.params; | ||||
|     const { firstName, lastName, email } = req.body; | ||||
|   | ||||
|     // Users can only update their own profile unless they're admin | ||||
|     <span class="missing-if-branch" title="if path not taken" >I</span>if (req.user.id !== id && <span class="branch-1 cbranch-no" title="branch not covered" >req.user.role !== 'admin')</span> { | ||||
| <span class="cstat-no" title="statement not covered" >      return res.status(403).json({ error: 'Access denied' });</span> | ||||
|     } | ||||
|   | ||||
|     // Check if email is already taken by another user | ||||
|     <span class="missing-if-branch" title="if path not taken" >I</span>if (email) { | ||||
|       const existingUser = <span class="cstat-no" title="statement not covered" >await pool.query(</span> | ||||
|         'SELECT id FROM users WHERE email = $1 AND id != $2', | ||||
|         [email, id] | ||||
|       ); | ||||
| <span class="cstat-no" title="statement not covered" >      if (existingUser.rows.length > 0) {</span> | ||||
| <span class="cstat-no" title="statement not covered" >        return res.status(400).json({ error: 'Email already in use' });</span> | ||||
|       } | ||||
|     } | ||||
|   | ||||
|     const updateFields = []; | ||||
|     const updateValues = []; | ||||
|     let paramCount = 1; | ||||
|   | ||||
|     <span class="missing-if-branch" title="else path not taken" >E</span>if (firstName) { | ||||
|       updateFields.push(`first_name = $${paramCount}`); | ||||
|       updateValues.push(firstName); | ||||
|       paramCount++; | ||||
|     } | ||||
|     <span class="missing-if-branch" title="if path not taken" >I</span>if (lastName) { | ||||
| <span class="cstat-no" title="statement not covered" >      updateFields.push(`last_name = $${paramCount}`);</span> | ||||
| <span class="cstat-no" title="statement not covered" >      updateValues.push(lastName);</span> | ||||
| <span class="cstat-no" title="statement not covered" >      paramCount++;</span> | ||||
|     } | ||||
|     <span class="missing-if-branch" title="if path not taken" >I</span>if (email) { | ||||
| <span class="cstat-no" title="statement not covered" >      updateFields.push(`email = $${paramCount}`);</span> | ||||
| <span class="cstat-no" title="statement not covered" >      updateValues.push(email);</span> | ||||
| <span class="cstat-no" title="statement not covered" >      paramCount++;</span> | ||||
|     } | ||||
|   | ||||
|     <span class="missing-if-branch" title="if path not taken" >I</span>if (updateFields.length === 0) { | ||||
| <span class="cstat-no" title="statement not covered" >      return res.status(400).json({ error: 'No fields to update' });</span> | ||||
|     } | ||||
|   | ||||
|     updateValues.push(id); | ||||
|     const query = `UPDATE users SET ${updateFields.join(', ')}, updated_at = CURRENT_TIMESTAMP WHERE id = $${paramCount} RETURNING id, email, first_name, last_name, role, is_active, updated_at`; | ||||
|   | ||||
|     const result = await pool.query(query, updateValues); | ||||
|   | ||||
|     res.json({ | ||||
|       message: 'User updated successfully', | ||||
|       user: result.rows[0] | ||||
|     }); | ||||
|   } catch (error) { | ||||
| <span class="cstat-no" title="statement not covered" >    console.error('Update user error:', error);</span> | ||||
| <span class="cstat-no" title="statement not covered" >    res.status(500).json({ error: 'Failed to update user' });</span> | ||||
|   } | ||||
| }); | ||||
|   | ||||
| // Deactivate user (admin only) | ||||
| router.put('/:id/deactivate', authenticateToken, requireRole(['admin']), async (req, res) => { | ||||
|   try { | ||||
|     const { id } = req.params; | ||||
|   | ||||
|     const result = await pool.query( | ||||
|       'UPDATE users SET is_active = false, updated_at = CURRENT_TIMESTAMP WHERE id = $1 RETURNING id, email, first_name, last_name, role, is_active', | ||||
|       [id] | ||||
|     ); | ||||
|   | ||||
|     <span class="missing-if-branch" title="if path not taken" >I</span>if (result.rows.length === 0) { | ||||
| <span class="cstat-no" title="statement not covered" >      return res.status(404).json({ error: 'User not found' });</span> | ||||
|     } | ||||
|   | ||||
|     res.json({ | ||||
|       message: 'User deactivated successfully', | ||||
|       user: result.rows[0] | ||||
|     }); | ||||
|   } catch (error) { | ||||
| <span class="cstat-no" title="statement not covered" >    console.error('Deactivate user error:', error);</span> | ||||
| <span class="cstat-no" title="statement not covered" >    res.status(500).json({ error: 'Failed to deactivate user' });</span> | ||||
|   } | ||||
| }); | ||||
|   | ||||
| // Activate user (admin only) | ||||
| router.put('/:id/activate', authenticateToken, requireRole(['admin']), async (req, res) => { | ||||
|   try { | ||||
|     const { id } = req.params; | ||||
|   | ||||
|     const result = await pool.query( | ||||
|       'UPDATE users SET is_active = true, updated_at = CURRENT_TIMESTAMP WHERE id = $1 RETURNING id, email, first_name, last_name, role, is_active', | ||||
|       [id] | ||||
|     ); | ||||
|   | ||||
|     <span class="missing-if-branch" title="if path not taken" >I</span>if (result.rows.length === 0) { | ||||
| <span class="cstat-no" title="statement not covered" >      return res.status(404).json({ error: 'User not found' });</span> | ||||
|     } | ||||
|   | ||||
|     res.json({ | ||||
|       message: 'User activated successfully', | ||||
|       user: result.rows[0] | ||||
|     }); | ||||
|   } catch (error) { | ||||
| <span class="cstat-no" title="statement not covered" >    console.error('Activate user error:', error);</span> | ||||
| <span class="cstat-no" title="statement not covered" >    res.status(500).json({ error: 'Failed to activate user' });</span> | ||||
|   } | ||||
| }); | ||||
|   | ||||
| module.exports = router; | ||||
|  </pre></td></tr></table></pre> | ||||
|  | ||||
|                 <div class='push'></div><!-- for sticky footer --> | ||||
|             </div><!-- /wrapper --> | ||||
|             <div class='footer quiet pad2 space-top1 center small'> | ||||
|                 Code coverage generated by | ||||
|                 <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> | ||||
|                 at 2025-10-17T03:41:07.369Z | ||||
|             </div> | ||||
|         <script src="../../prettify.js"></script> | ||||
|         <script> | ||||
|             window.onload = function () { | ||||
|                 prettyPrint(); | ||||
|             }; | ||||
|         </script> | ||||
|         <script src="../../sorter.js"></script> | ||||
|         <script src="../../block-navigation.js"></script> | ||||
|     </body> | ||||
| </html> | ||||
|      | ||||
							
								
								
									
										271
									
								
								backend/coverage/lcov-report/src/server.js.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										271
									
								
								backend/coverage/lcov-report/src/server.js.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,271 @@ | ||||
|  | ||||
| <!doctype html> | ||||
| <html lang="en"> | ||||
|  | ||||
| <head> | ||||
|     <title>Code coverage report for src/server.js</title> | ||||
|     <meta charset="utf-8" /> | ||||
|     <link rel="stylesheet" href="../prettify.css" /> | ||||
|     <link rel="stylesheet" href="../base.css" /> | ||||
|     <link rel="shortcut icon" type="image/x-icon" href="../favicon.png" /> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1" /> | ||||
|     <style type='text/css'> | ||||
|         .coverage-summary .sorter { | ||||
|             background-image: url(../sort-arrow-sprite.png); | ||||
|         } | ||||
|     </style> | ||||
| </head> | ||||
|      | ||||
| <body> | ||||
| <div class='wrapper'> | ||||
|     <div class='pad1'> | ||||
|         <h1><a href="../index.html">All files</a> / <a href="index.html">src</a> server.js</h1> | ||||
|         <div class='clearfix'> | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">86.84% </span> | ||||
|                 <span class="quiet">Statements</span> | ||||
|                 <span class='fraction'>33/38</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">37.5% </span> | ||||
|                 <span class="quiet">Branches</span> | ||||
|                 <span class='fraction'>3/8</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">0% </span> | ||||
|                 <span class="quiet">Functions</span> | ||||
|                 <span class='fraction'>0/4</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">86.84% </span> | ||||
|                 <span class="quiet">Lines</span> | ||||
|                 <span class='fraction'>33/38</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|         </div> | ||||
|         <p class="quiet"> | ||||
|             Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block. | ||||
|         </p> | ||||
|         <template id="filterTemplate"> | ||||
|             <div class="quiet"> | ||||
|                 Filter: | ||||
|                 <input type="search" id="fileSearch"> | ||||
|             </div> | ||||
|         </template> | ||||
|     </div> | ||||
|     <div class='status-line high'></div> | ||||
|     <pre><table class="coverage"> | ||||
| <tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a> | ||||
| <a name='L2'></a><a href='#L2'>2</a> | ||||
| <a name='L3'></a><a href='#L3'>3</a> | ||||
| <a name='L4'></a><a href='#L4'>4</a> | ||||
| <a name='L5'></a><a href='#L5'>5</a> | ||||
| <a name='L6'></a><a href='#L6'>6</a> | ||||
| <a name='L7'></a><a href='#L7'>7</a> | ||||
| <a name='L8'></a><a href='#L8'>8</a> | ||||
| <a name='L9'></a><a href='#L9'>9</a> | ||||
| <a name='L10'></a><a href='#L10'>10</a> | ||||
| <a name='L11'></a><a href='#L11'>11</a> | ||||
| <a name='L12'></a><a href='#L12'>12</a> | ||||
| <a name='L13'></a><a href='#L13'>13</a> | ||||
| <a name='L14'></a><a href='#L14'>14</a> | ||||
| <a name='L15'></a><a href='#L15'>15</a> | ||||
| <a name='L16'></a><a href='#L16'>16</a> | ||||
| <a name='L17'></a><a href='#L17'>17</a> | ||||
| <a name='L18'></a><a href='#L18'>18</a> | ||||
| <a name='L19'></a><a href='#L19'>19</a> | ||||
| <a name='L20'></a><a href='#L20'>20</a> | ||||
| <a name='L21'></a><a href='#L21'>21</a> | ||||
| <a name='L22'></a><a href='#L22'>22</a> | ||||
| <a name='L23'></a><a href='#L23'>23</a> | ||||
| <a name='L24'></a><a href='#L24'>24</a> | ||||
| <a name='L25'></a><a href='#L25'>25</a> | ||||
| <a name='L26'></a><a href='#L26'>26</a> | ||||
| <a name='L27'></a><a href='#L27'>27</a> | ||||
| <a name='L28'></a><a href='#L28'>28</a> | ||||
| <a name='L29'></a><a href='#L29'>29</a> | ||||
| <a name='L30'></a><a href='#L30'>30</a> | ||||
| <a name='L31'></a><a href='#L31'>31</a> | ||||
| <a name='L32'></a><a href='#L32'>32</a> | ||||
| <a name='L33'></a><a href='#L33'>33</a> | ||||
| <a name='L34'></a><a href='#L34'>34</a> | ||||
| <a name='L35'></a><a href='#L35'>35</a> | ||||
| <a name='L36'></a><a href='#L36'>36</a> | ||||
| <a name='L37'></a><a href='#L37'>37</a> | ||||
| <a name='L38'></a><a href='#L38'>38</a> | ||||
| <a name='L39'></a><a href='#L39'>39</a> | ||||
| <a name='L40'></a><a href='#L40'>40</a> | ||||
| <a name='L41'></a><a href='#L41'>41</a> | ||||
| <a name='L42'></a><a href='#L42'>42</a> | ||||
| <a name='L43'></a><a href='#L43'>43</a> | ||||
| <a name='L44'></a><a href='#L44'>44</a> | ||||
| <a name='L45'></a><a href='#L45'>45</a> | ||||
| <a name='L46'></a><a href='#L46'>46</a> | ||||
| <a name='L47'></a><a href='#L47'>47</a> | ||||
| <a name='L48'></a><a href='#L48'>48</a> | ||||
| <a name='L49'></a><a href='#L49'>49</a> | ||||
| <a name='L50'></a><a href='#L50'>50</a> | ||||
| <a name='L51'></a><a href='#L51'>51</a> | ||||
| <a name='L52'></a><a href='#L52'>52</a> | ||||
| <a name='L53'></a><a href='#L53'>53</a> | ||||
| <a name='L54'></a><a href='#L54'>54</a> | ||||
| <a name='L55'></a><a href='#L55'>55</a> | ||||
| <a name='L56'></a><a href='#L56'>56</a> | ||||
| <a name='L57'></a><a href='#L57'>57</a> | ||||
| <a name='L58'></a><a href='#L58'>58</a> | ||||
| <a name='L59'></a><a href='#L59'>59</a> | ||||
| <a name='L60'></a><a href='#L60'>60</a> | ||||
| <a name='L61'></a><a href='#L61'>61</a> | ||||
| <a name='L62'></a><a href='#L62'>62</a> | ||||
| <a name='L63'></a><a href='#L63'>63</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">const express = require('express'); | ||||
| const cors = require('cors'); | ||||
| const helmet = require('helmet'); | ||||
| const morgan = require('morgan'); | ||||
| const rateLimit = require('express-rate-limit'); | ||||
| const config = require('./config'); | ||||
|   | ||||
| const authRoutes = require('./routes/auth'); | ||||
| const userRoutes = require('./routes/users'); | ||||
| const employerRoutes = require('./routes/employers'); | ||||
| const candidateRoutes = require('./routes/candidates'); | ||||
| const jobRoutes = require('./routes/jobs'); | ||||
| const applicationRoutes = require('./routes/applications'); | ||||
| const resumeRoutes = require('./routes/resumes'); | ||||
|   | ||||
| const app = express(); | ||||
|   | ||||
| const authLimiter = rateLimit({ | ||||
|   windowMs: config.rateLimit.windowMs, | ||||
|   max: config.rateLimit.max, | ||||
|   standardHeaders: true, | ||||
|   legacyHeaders: false | ||||
| }); | ||||
|   | ||||
| app.disable('x-powered-by'); | ||||
|   | ||||
| const corsOrigins = config.corsOrigin === '*' | ||||
|   ? undefined | ||||
|   : <span class="branch-1 cbranch-no" title="branch not covered" >config.corsOrigin.split(',').map(<span class="fstat-no" title="function not covered" >(o</span>rigin) => <span class="cstat-no" title="statement not covered" >origin.trim())</span>;</span> | ||||
|   | ||||
| app.use(helmet()); | ||||
| app.use(cors(corsOrigins ? <span class="branch-0 cbranch-no" title="branch not covered" >{ origin: corsOrigins, credentials: true } </span>: {})); | ||||
| app.use(morgan(config.env === 'production' ? <span class="branch-0 cbranch-no" title="branch not covered" >'combined' </span>: 'dev')); | ||||
| app.use(express.json({ limit: '10mb' })); | ||||
| app.use(express.urlencoded({ extended: true })); | ||||
|   | ||||
| app.use('/api/auth', authLimiter, authRoutes); | ||||
| app.use('/api/users', userRoutes); | ||||
| app.use('/api/employers', employerRoutes); | ||||
| app.use('/api/candidates', candidateRoutes); | ||||
| app.use('/api/jobs', jobRoutes); | ||||
| app.use('/api/applications', applicationRoutes); | ||||
| app.use('/api/resumes', resumeRoutes); | ||||
|   | ||||
| app.get('/api/health', <span class="fstat-no" title="function not covered" >(r</span>eq, res) => { | ||||
| <span class="cstat-no" title="statement not covered" >  res.json({ status: 'OK', timestamp: new Date().toISOString() });</span> | ||||
| }); | ||||
|   | ||||
| // eslint-disable-next-line no-unused-vars | ||||
| app.use(<span class="fstat-no" title="function not covered" >(e</span>rr, req, res, next) => { | ||||
| <span class="cstat-no" title="statement not covered" >  console.error(err.stack);</span> | ||||
| <span class="cstat-no" title="statement not covered" >  res.status(500).json({</span> | ||||
|     error: 'Something went wrong!', | ||||
|     message: config.env === 'development' ? err.message : 'Internal server error' | ||||
|   }); | ||||
| }); | ||||
|   | ||||
| app.use('*', <span class="fstat-no" title="function not covered" >(r</span>eq, res) => { | ||||
| <span class="cstat-no" title="statement not covered" >  res.status(404).json({ error: 'Route not found' });</span> | ||||
| }); | ||||
|   | ||||
| module.exports = app; | ||||
|  </pre></td></tr></table></pre> | ||||
|  | ||||
|                 <div class='push'></div><!-- for sticky footer --> | ||||
|             </div><!-- /wrapper --> | ||||
|             <div class='footer quiet pad2 space-top1 center small'> | ||||
|                 Code coverage generated by | ||||
|                 <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> | ||||
|                 at 2025-10-17T03:41:07.369Z | ||||
|             </div> | ||||
|         <script src="../prettify.js"></script> | ||||
|         <script> | ||||
|             window.onload = function () { | ||||
|                 prettyPrint(); | ||||
|             }; | ||||
|         </script> | ||||
|         <script src="../sorter.js"></script> | ||||
|         <script src="../block-navigation.js"></script> | ||||
|     </body> | ||||
| </html> | ||||
|      | ||||
							
								
								
									
										116
									
								
								backend/coverage/lcov-report/src/tests/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								backend/coverage/lcov-report/src/tests/index.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,116 @@ | ||||
|  | ||||
| <!doctype html> | ||||
| <html lang="en"> | ||||
|  | ||||
| <head> | ||||
|     <title>Code coverage report for src/tests</title> | ||||
|     <meta charset="utf-8" /> | ||||
|     <link rel="stylesheet" href="../../prettify.css" /> | ||||
|     <link rel="stylesheet" href="../../base.css" /> | ||||
|     <link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" /> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1" /> | ||||
|     <style type='text/css'> | ||||
|         .coverage-summary .sorter { | ||||
|             background-image: url(../../sort-arrow-sprite.png); | ||||
|         } | ||||
|     </style> | ||||
| </head> | ||||
|      | ||||
| <body> | ||||
| <div class='wrapper'> | ||||
|     <div class='pad1'> | ||||
|         <h1><a href="../../index.html">All files</a> src/tests</h1> | ||||
|         <div class='clearfix'> | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">86.11% </span> | ||||
|                 <span class="quiet">Statements</span> | ||||
|                 <span class='fraction'>31/36</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">86.66% </span> | ||||
|                 <span class="quiet">Branches</span> | ||||
|                 <span class='fraction'>26/30</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">100% </span> | ||||
|                 <span class="quiet">Functions</span> | ||||
|                 <span class='fraction'>6/6</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">85.71% </span> | ||||
|                 <span class="quiet">Lines</span> | ||||
|                 <span class='fraction'>30/35</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|         </div> | ||||
|         <p class="quiet"> | ||||
|             Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block. | ||||
|         </p> | ||||
|         <template id="filterTemplate"> | ||||
|             <div class="quiet"> | ||||
|                 Filter: | ||||
|                 <input type="search" id="fileSearch"> | ||||
|             </div> | ||||
|         </template> | ||||
|     </div> | ||||
|     <div class='status-line high'></div> | ||||
|     <div class="pad1"> | ||||
| <table class="coverage-summary"> | ||||
| <thead> | ||||
| <tr> | ||||
|    <th data-col="file" data-fmt="html" data-html="true" class="file">File</th> | ||||
|    <th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th> | ||||
|    <th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th> | ||||
|    <th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th> | ||||
|    <th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th> | ||||
|    <th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th> | ||||
|    <th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th> | ||||
|    <th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th> | ||||
|    <th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th> | ||||
|    <th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th> | ||||
| </tr> | ||||
| </thead> | ||||
| <tbody><tr> | ||||
| 	<td class="file high" data-value="utils.js"><a href="utils.js.html">utils.js</a></td> | ||||
| 	<td data-value="86.11" class="pic high"> | ||||
| 	<div class="chart"><div class="cover-fill" style="width: 86%"></div><div class="cover-empty" style="width: 14%"></div></div> | ||||
| 	</td> | ||||
| 	<td data-value="86.11" class="pct high">86.11%</td> | ||||
| 	<td data-value="36" class="abs high">31/36</td> | ||||
| 	<td data-value="86.66" class="pct high">86.66%</td> | ||||
| 	<td data-value="30" class="abs high">26/30</td> | ||||
| 	<td data-value="100" class="pct high">100%</td> | ||||
| 	<td data-value="6" class="abs high">6/6</td> | ||||
| 	<td data-value="85.71" class="pct high">85.71%</td> | ||||
| 	<td data-value="35" class="abs high">30/35</td> | ||||
| 	</tr> | ||||
|  | ||||
| </tbody> | ||||
| </table> | ||||
| </div> | ||||
|                 <div class='push'></div><!-- for sticky footer --> | ||||
|             </div><!-- /wrapper --> | ||||
|             <div class='footer quiet pad2 space-top1 center small'> | ||||
|                 Code coverage generated by | ||||
|                 <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> | ||||
|                 at 2025-10-17T03:41:07.369Z | ||||
|             </div> | ||||
|         <script src="../../prettify.js"></script> | ||||
|         <script> | ||||
|             window.onload = function () { | ||||
|                 prettyPrint(); | ||||
|             }; | ||||
|         </script> | ||||
|         <script src="../../sorter.js"></script> | ||||
|         <script src="../../block-navigation.js"></script> | ||||
|     </body> | ||||
| </html> | ||||
|      | ||||
							
								
								
									
										421
									
								
								backend/coverage/lcov-report/src/tests/utils.js.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										421
									
								
								backend/coverage/lcov-report/src/tests/utils.js.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,421 @@ | ||||
|  | ||||
| <!doctype html> | ||||
| <html lang="en"> | ||||
|  | ||||
| <head> | ||||
|     <title>Code coverage report for src/tests/utils.js</title> | ||||
|     <meta charset="utf-8" /> | ||||
|     <link rel="stylesheet" href="../../prettify.css" /> | ||||
|     <link rel="stylesheet" href="../../base.css" /> | ||||
|     <link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" /> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1" /> | ||||
|     <style type='text/css'> | ||||
|         .coverage-summary .sorter { | ||||
|             background-image: url(../../sort-arrow-sprite.png); | ||||
|         } | ||||
|     </style> | ||||
| </head> | ||||
|      | ||||
| <body> | ||||
| <div class='wrapper'> | ||||
|     <div class='pad1'> | ||||
|         <h1><a href="../../index.html">All files</a> / <a href="index.html">src/tests</a> utils.js</h1> | ||||
|         <div class='clearfix'> | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">86.11% </span> | ||||
|                 <span class="quiet">Statements</span> | ||||
|                 <span class='fraction'>31/36</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">86.66% </span> | ||||
|                 <span class="quiet">Branches</span> | ||||
|                 <span class='fraction'>26/30</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">100% </span> | ||||
|                 <span class="quiet">Functions</span> | ||||
|                 <span class='fraction'>6/6</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|             <div class='fl pad1y space-right2'> | ||||
|                 <span class="strong">85.71% </span> | ||||
|                 <span class="quiet">Lines</span> | ||||
|                 <span class='fraction'>30/35</span> | ||||
|             </div> | ||||
|          | ||||
|              | ||||
|         </div> | ||||
|         <p class="quiet"> | ||||
|             Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block. | ||||
|         </p> | ||||
|         <template id="filterTemplate"> | ||||
|             <div class="quiet"> | ||||
|                 Filter: | ||||
|                 <input type="search" id="fileSearch"> | ||||
|             </div> | ||||
|         </template> | ||||
|     </div> | ||||
|     <div class='status-line high'></div> | ||||
|     <pre><table class="coverage"> | ||||
| <tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a> | ||||
| <a name='L2'></a><a href='#L2'>2</a> | ||||
| <a name='L3'></a><a href='#L3'>3</a> | ||||
| <a name='L4'></a><a href='#L4'>4</a> | ||||
| <a name='L5'></a><a href='#L5'>5</a> | ||||
| <a name='L6'></a><a href='#L6'>6</a> | ||||
| <a name='L7'></a><a href='#L7'>7</a> | ||||
| <a name='L8'></a><a href='#L8'>8</a> | ||||
| <a name='L9'></a><a href='#L9'>9</a> | ||||
| <a name='L10'></a><a href='#L10'>10</a> | ||||
| <a name='L11'></a><a href='#L11'>11</a> | ||||
| <a name='L12'></a><a href='#L12'>12</a> | ||||
| <a name='L13'></a><a href='#L13'>13</a> | ||||
| <a name='L14'></a><a href='#L14'>14</a> | ||||
| <a name='L15'></a><a href='#L15'>15</a> | ||||
| <a name='L16'></a><a href='#L16'>16</a> | ||||
| <a name='L17'></a><a href='#L17'>17</a> | ||||
| <a name='L18'></a><a href='#L18'>18</a> | ||||
| <a name='L19'></a><a href='#L19'>19</a> | ||||
| <a name='L20'></a><a href='#L20'>20</a> | ||||
| <a name='L21'></a><a href='#L21'>21</a> | ||||
| <a name='L22'></a><a href='#L22'>22</a> | ||||
| <a name='L23'></a><a href='#L23'>23</a> | ||||
| <a name='L24'></a><a href='#L24'>24</a> | ||||
| <a name='L25'></a><a href='#L25'>25</a> | ||||
| <a name='L26'></a><a href='#L26'>26</a> | ||||
| <a name='L27'></a><a href='#L27'>27</a> | ||||
| <a name='L28'></a><a href='#L28'>28</a> | ||||
| <a name='L29'></a><a href='#L29'>29</a> | ||||
| <a name='L30'></a><a href='#L30'>30</a> | ||||
| <a name='L31'></a><a href='#L31'>31</a> | ||||
| <a name='L32'></a><a href='#L32'>32</a> | ||||
| <a name='L33'></a><a href='#L33'>33</a> | ||||
| <a name='L34'></a><a href='#L34'>34</a> | ||||
| <a name='L35'></a><a href='#L35'>35</a> | ||||
| <a name='L36'></a><a href='#L36'>36</a> | ||||
| <a name='L37'></a><a href='#L37'>37</a> | ||||
| <a name='L38'></a><a href='#L38'>38</a> | ||||
| <a name='L39'></a><a href='#L39'>39</a> | ||||
| <a name='L40'></a><a href='#L40'>40</a> | ||||
| <a name='L41'></a><a href='#L41'>41</a> | ||||
| <a name='L42'></a><a href='#L42'>42</a> | ||||
| <a name='L43'></a><a href='#L43'>43</a> | ||||
| <a name='L44'></a><a href='#L44'>44</a> | ||||
| <a name='L45'></a><a href='#L45'>45</a> | ||||
| <a name='L46'></a><a href='#L46'>46</a> | ||||
| <a name='L47'></a><a href='#L47'>47</a> | ||||
| <a name='L48'></a><a href='#L48'>48</a> | ||||
| <a name='L49'></a><a href='#L49'>49</a> | ||||
| <a name='L50'></a><a href='#L50'>50</a> | ||||
| <a name='L51'></a><a href='#L51'>51</a> | ||||
| <a name='L52'></a><a href='#L52'>52</a> | ||||
| <a name='L53'></a><a href='#L53'>53</a> | ||||
| <a name='L54'></a><a href='#L54'>54</a> | ||||
| <a name='L55'></a><a href='#L55'>55</a> | ||||
| <a name='L56'></a><a href='#L56'>56</a> | ||||
| <a name='L57'></a><a href='#L57'>57</a> | ||||
| <a name='L58'></a><a href='#L58'>58</a> | ||||
| <a name='L59'></a><a href='#L59'>59</a> | ||||
| <a name='L60'></a><a href='#L60'>60</a> | ||||
| <a name='L61'></a><a href='#L61'>61</a> | ||||
| <a name='L62'></a><a href='#L62'>62</a> | ||||
| <a name='L63'></a><a href='#L63'>63</a> | ||||
| <a name='L64'></a><a href='#L64'>64</a> | ||||
| <a name='L65'></a><a href='#L65'>65</a> | ||||
| <a name='L66'></a><a href='#L66'>66</a> | ||||
| <a name='L67'></a><a href='#L67'>67</a> | ||||
| <a name='L68'></a><a href='#L68'>68</a> | ||||
| <a name='L69'></a><a href='#L69'>69</a> | ||||
| <a name='L70'></a><a href='#L70'>70</a> | ||||
| <a name='L71'></a><a href='#L71'>71</a> | ||||
| <a name='L72'></a><a href='#L72'>72</a> | ||||
| <a name='L73'></a><a href='#L73'>73</a> | ||||
| <a name='L74'></a><a href='#L74'>74</a> | ||||
| <a name='L75'></a><a href='#L75'>75</a> | ||||
| <a name='L76'></a><a href='#L76'>76</a> | ||||
| <a name='L77'></a><a href='#L77'>77</a> | ||||
| <a name='L78'></a><a href='#L78'>78</a> | ||||
| <a name='L79'></a><a href='#L79'>79</a> | ||||
| <a name='L80'></a><a href='#L80'>80</a> | ||||
| <a name='L81'></a><a href='#L81'>81</a> | ||||
| <a name='L82'></a><a href='#L82'>82</a> | ||||
| <a name='L83'></a><a href='#L83'>83</a> | ||||
| <a name='L84'></a><a href='#L84'>84</a> | ||||
| <a name='L85'></a><a href='#L85'>85</a> | ||||
| <a name='L86'></a><a href='#L86'>86</a> | ||||
| <a name='L87'></a><a href='#L87'>87</a> | ||||
| <a name='L88'></a><a href='#L88'>88</a> | ||||
| <a name='L89'></a><a href='#L89'>89</a> | ||||
| <a name='L90'></a><a href='#L90'>90</a> | ||||
| <a name='L91'></a><a href='#L91'>91</a> | ||||
| <a name='L92'></a><a href='#L92'>92</a> | ||||
| <a name='L93'></a><a href='#L93'>93</a> | ||||
| <a name='L94'></a><a href='#L94'>94</a> | ||||
| <a name='L95'></a><a href='#L95'>95</a> | ||||
| <a name='L96'></a><a href='#L96'>96</a> | ||||
| <a name='L97'></a><a href='#L97'>97</a> | ||||
| <a name='L98'></a><a href='#L98'>98</a> | ||||
| <a name='L99'></a><a href='#L99'>99</a> | ||||
| <a name='L100'></a><a href='#L100'>100</a> | ||||
| <a name='L101'></a><a href='#L101'>101</a> | ||||
| <a name='L102'></a><a href='#L102'>102</a> | ||||
| <a name='L103'></a><a href='#L103'>103</a> | ||||
| <a name='L104'></a><a href='#L104'>104</a> | ||||
| <a name='L105'></a><a href='#L105'>105</a> | ||||
| <a name='L106'></a><a href='#L106'>106</a> | ||||
| <a name='L107'></a><a href='#L107'>107</a> | ||||
| <a name='L108'></a><a href='#L108'>108</a> | ||||
| <a name='L109'></a><a href='#L109'>109</a> | ||||
| <a name='L110'></a><a href='#L110'>110</a> | ||||
| <a name='L111'></a><a href='#L111'>111</a> | ||||
| <a name='L112'></a><a href='#L112'>112</a> | ||||
| <a name='L113'></a><a href='#L113'>113</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">16x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">15x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">15x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">15x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">11x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">11x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">11x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">1x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">24x</span> | ||||
| <span class="cline-any cline-yes">24x</span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">24x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">23x</span> | ||||
| <span class="cline-any cline-yes">23x</span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">23x</span> | ||||
| <span class="cline-any cline-yes">23x</span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-no"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-yes">7x</span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span> | ||||
| <span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">const { randomUUID } = require('crypto'); | ||||
| const path = require('path'); | ||||
| const fs = require('fs'); | ||||
| const bcrypt = require('bcryptjs'); | ||||
| const request = require('supertest'); | ||||
| const app = require('../server'); | ||||
| const config = require('../config'); | ||||
| const pool = require('../database/connection'); | ||||
|   | ||||
| const uniqueEmail = (prefix = <span class="branch-0 cbranch-no" title="branch not covered" >'user')</span> => `${prefix}-${randomUUID()}@example.com`; | ||||
|   | ||||
| const defaultPassword = 'Password123!'; | ||||
|   | ||||
| async function registerUser(role = <span class="branch-0 cbranch-no" title="branch not covered" >'candidate',</span> overrides = {}) { | ||||
|   const userData = { | ||||
|     firstName: overrides.firstName || 'Test', | ||||
|     lastName: overrides.lastName || 'User', | ||||
|     email: overrides.email || uniqueEmail(role), | ||||
|     password: overrides.password || defaultPassword, | ||||
|     role, | ||||
|     ...overrides | ||||
|   }; | ||||
|   | ||||
|   const response = await request(app) | ||||
|     .post('/api/auth/register') | ||||
|     .send(userData); | ||||
|   | ||||
|   return { | ||||
|     token: response.body.token, | ||||
|     user: response.body.user, | ||||
|     credentials: { | ||||
|       email: userData.email, | ||||
|       password: userData.password | ||||
|     } | ||||
|   }; | ||||
| } | ||||
|   | ||||
| async function createEmployerProfile(token, overrides = {}) { | ||||
|   const employerData = { | ||||
|     companyName: overrides.companyName || `Company ${randomUUID().slice(0, 8)}`, | ||||
|     industry: overrides.industry || 'Technology', | ||||
|     companySize: overrides.companySize || '11-50', | ||||
|     website: overrides.website || 'https://example.com', | ||||
|     description: overrides.description || 'Test company description', | ||||
|     address: overrides.address || '123 Test St', | ||||
|     phone: overrides.phone || '+1-555-0000', | ||||
|     ...overrides | ||||
|   }; | ||||
|   | ||||
|   const response = await request(app) | ||||
|     .post('/api/employers') | ||||
|     .set('Authorization', `Bearer ${token}`) | ||||
|     .send(employerData); | ||||
|   | ||||
|   return response.body.employer; | ||||
| } | ||||
|   | ||||
| async function createAdminUser() { | ||||
|   const email = uniqueEmail('admin'); | ||||
|   const password = defaultPassword; | ||||
|   const passwordHash = await bcrypt.hash(password, 10); | ||||
|   | ||||
|   const result = await pool.query( | ||||
|     'INSERT INTO users (email, password_hash, first_name, last_name, role) VALUES ($1, $2, $3, $4, $5) RETURNING id', | ||||
|     [email, passwordHash, 'Admin', 'User', 'admin'] | ||||
|   ); | ||||
|   | ||||
|   const loginResponse = await request(app) | ||||
|     .post('/api/auth/login') | ||||
|     .send({ email, password }); | ||||
|   | ||||
|   return { | ||||
|     token: loginResponse.body.token, | ||||
|     user: loginResponse.body.user, | ||||
|     userId: result.rows[0].id, | ||||
|     credentials: { email, password } | ||||
|   }; | ||||
| } | ||||
|   | ||||
| async function ensureUploadDir() { | ||||
|   const uploadDir = path.join(__dirname, '..', '..', config.uploadDir); | ||||
|   <span class="missing-if-branch" title="if path not taken" >I</span>if (!fs.existsSync(uploadDir)) { | ||||
| <span class="cstat-no" title="statement not covered" >    fs.mkdirSync(uploadDir, { recursive: true });</span> | ||||
|   } | ||||
|   return uploadDir; | ||||
| } | ||||
|   | ||||
| async function cleanupUploads() { | ||||
|   const uploadDir = await ensureUploadDir(); | ||||
|   <span class="missing-if-branch" title="if path not taken" >I</span>if (!fs.existsSync(uploadDir)) { | ||||
| <span class="cstat-no" title="statement not covered" >    return;</span> | ||||
|   } | ||||
|   const entries = fs.readdirSync(uploadDir); | ||||
|   for (const entry of entries) { | ||||
|     const filePath = <span class="cstat-no" title="statement not covered" >path.join(uploadDir, entry);</span> | ||||
| <span class="cstat-no" title="statement not covered" >    try {</span> | ||||
| <span class="cstat-no" title="statement not covered" >      fs.unlinkSync(filePath);</span> | ||||
|     } catch (error) { | ||||
|       // Ignore deletion errors in tests | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
| module.exports = { | ||||
|   registerUser, | ||||
|   createEmployerProfile, | ||||
|   createAdminUser, | ||||
|   uniqueEmail, | ||||
|   defaultPassword, | ||||
|   ensureUploadDir, | ||||
|   cleanupUploads | ||||
| }; | ||||
|  </pre></td></tr></table></pre> | ||||
|  | ||||
|                 <div class='push'></div><!-- for sticky footer --> | ||||
|             </div><!-- /wrapper --> | ||||
|             <div class='footer quiet pad2 space-top1 center small'> | ||||
|                 Code coverage generated by | ||||
|                 <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> | ||||
|                 at 2025-10-17T03:41:07.369Z | ||||
|             </div> | ||||
|         <script src="../../prettify.js"></script> | ||||
|         <script> | ||||
|             window.onload = function () { | ||||
|                 prettyPrint(); | ||||
|             }; | ||||
|         </script> | ||||
|         <script src="../../sorter.js"></script> | ||||
|         <script src="../../block-navigation.js"></script> | ||||
|     </body> | ||||
| </html> | ||||
|      | ||||
							
								
								
									
										1768
									
								
								backend/coverage/lcov.info
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1768
									
								
								backend/coverage/lcov.info
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Reference in New Issue
	
	Block a user