From 4e366ea5d254d08ff2d3fe7ffcb4a125233cb774 Mon Sep 17 00:00:00 2001 From: eric-gade Date: Wed, 24 Apr 2024 14:56:33 -0400 Subject: [PATCH 01/11] Initial implementation with the first few unit tests --- .../Service/Test/WeatherAlertParser.php.test | 82 +++++++++++++++---- .../src/Service/WeatherAlertParser.php | 29 +++++++ 2 files changed, 94 insertions(+), 17 deletions(-) diff --git a/web/modules/weather_data/src/Service/Test/WeatherAlertParser.php.test b/web/modules/weather_data/src/Service/Test/WeatherAlertParser.php.test index ca3a0d792..a561b00ce 100644 --- a/web/modules/weather_data/src/Service/Test/WeatherAlertParser.php.test +++ b/web/modules/weather_data/src/Service/Test/WeatherAlertParser.php.test @@ -148,13 +148,13 @@ Wednesday, then drop to near 5500 feet Wednesday night and near "type" => "paragraph", "text" => ".After a few days of warm weather, a potent winter storm" . - " will bring windy and colder conditions with periods of" . - " heavy snow to the Sierra and higher elevations of northeast" . - " California later this week. While weather-related travel impacts" . - " aren't expected through Wednesday morning, conditions will" . - " begin to worsen Wednesday afternoon and evening, with the most" . - " widespread winter travel impacts likely from Wednesday" . - " evening through much of Thursday.", + " will bring windy and colder conditions with periods of" . + " heavy snow to the Sierra and higher elevations of northeast" . + " California later this week. While weather-related travel impacts" . + " aren't expected through Wednesday morning, conditions will" . + " begin to worsen Wednesday afternoon and evening, with the most" . + " widespread winter travel impacts likely from Wednesday" . + " evening through much of Thursday.", ], [ "type" => "heading", @@ -164,9 +164,9 @@ Wednesday, then drop to near 5500 feet Wednesday night and near "type" => "paragraph", "text" => "Heavy snow possible. Snow accumulations of 4 to 10 inches" . - " above 5000 feet west of US-395, with 10 to 20 inches possible" . - " for higher passes such as Fredonyer Summit and Yuba Pass." . - " Winds gusting as high as 50 mph.", + " above 5000 feet west of US-395, with 10 to 20 inches possible" . + " for higher passes such as Fredonyer Summit and Yuba Pass." . + " Winds gusting as high as 50 mph.", ], [ "type" => "heading", @@ -184,7 +184,7 @@ Wednesday, then drop to near 5500 feet Wednesday night and near "type" => "paragraph", "text" => "From late Wednesday morning through Friday morning." . - " Heaviest snow is most likely from late Wednesday afternoon through Thursday morning.", + " Heaviest snow is most likely from late Wednesday afternoon through Thursday morning.", ], [ "type" => "heading", @@ -194,10 +194,10 @@ Wednesday, then drop to near 5500 feet Wednesday night and near "type" => "paragraph", "text" => "Travel could be very difficult at times," . - " with hazardous conditions impacting the commutes from" . - " Wednesday evening through Friday morning. Strong winds" . - " may blow down some tree limbs and a few power outages" . - " may result.", + " with hazardous conditions impacting the commutes from" . + " Wednesday evening through Friday morning. Strong winds" . + " may blow down some tree limbs and a few power outages" . + " may result.", ], [ "type" => "heading", @@ -207,8 +207,8 @@ Wednesday, then drop to near 5500 feet Wednesday night and near "type" => "paragraph", "text" => "Snow levels will start near 6500 feet on Wednesday," . - " then drop to near 5500 feet Wednesday night and near" . - " 5000 feet by Thursday morning.", + " then drop to near 5500 feet Wednesday night and near" . + " 5000 feet by Thursday morning.", ], ]; @@ -217,4 +217,52 @@ Wednesday, then drop to near 5500 feet Wednesday night and near $this->assertEquals($expected, $actual); } + + /** + * @group unit + * @group alert-parser + */ + public function testParsesBasicGovUrl() + { + $sourceText = "Scattered showers with brief heavy rainfall will continue this week. +A low pressure system from the north will batter your area, relentlessly. +Be sure to stock up. See https://www.weather.gov/safety/food for more information."; + $parser = new WeatherAlertParser(""); + $actual = $parser->extractURLs($sourceText); + $expectedUrl = "https://www.weather.gov/safety/food"; + + $this->assertEquals(1, count($actual)); + $this->assertEquals($expectedUrl, $actual[0]); + } + + /** + * @group unit + * @group alert-parser + */ + public function testIgnoresNonGovUrls() + { + $sourceText = "Scattered showers with brief heavy rainfall will go on and +on and on, boring everyone who has to stay inside. They will attempt to visit sites +like https://fake.com/should/not/render which will not be clickable URLs."; + $parser = new WeatherAlertParser(""); + $actual = $parser->extractURLs($sourceText); + + $this->assertEquals(false, $actual); + } + + /** + * @group unit + * @group alert-parser + */ + public function testIgnoresUserPassUrls() + { + $sourceText = "Weather will, once again, be horrendous wherever you happen to be. +Maybe you are cursed? Anyway, the spammers would like you to click this link here that +has a username and password in the URL (https://root:1234@weather.gov/hack/the/gibson), which +you shouldn't be able to do thanks to Cautious Public Servants TM."; + $parser = new WeatherAlertParser(""); + $actual = $parser->extractURLs($sourceText); + + $this->assertEquals(false, $actual); + } } diff --git a/web/modules/weather_data/src/Service/WeatherAlertParser.php b/web/modules/weather_data/src/Service/WeatherAlertParser.php index 11b131b95..afc0d4265 100644 --- a/web/modules/weather_data/src/Service/WeatherAlertParser.php +++ b/web/modules/weather_data/src/Service/WeatherAlertParser.php @@ -124,4 +124,33 @@ public function parseWhatWhereWhen($str) return false; } + + /** + * Attemps to parse out any valid URLs in the provided + * text body. + * Responds with a list of parsed objects if any are found, + * or false if none are found. + */ + public function extractURLs($str) + { + $regex = "/https\:\/\/[A-Za-z0-9\-._~:\/\?#\[\]@!$]+/"; + if(preg_match_all($regex, $str, $matches)){ + $valid = array_filter($matches[0], function($urlString){ + $url = parse_url($urlString); + if(array_key_exists("user", $url)){ + return false; + } else if(array_key_exists("pass", $url)){ + return false; + } else if(!str_ends_with($url["host"], ".gov")){ + return false; + } + return true; + }); + if(count($valid) == 0){ + return false; + } + return array_values($valid); + } + return false; + } } From 369415f366ac6686dcc214c42a28649c37476acf Mon Sep 17 00:00:00 2001 From: eric-gade Date: Tue, 7 May 2024 15:01:18 -0400 Subject: [PATCH 02/11] We're cooking with gas now --- .../Service/Test/WeatherAlertParser.php.test | 606 +++++++++++------- .../src/Service/WeatherAlertParser.php | 80 ++- 2 files changed, 462 insertions(+), 224 deletions(-) diff --git a/web/modules/weather_data/src/Service/Test/WeatherAlertParser.php.test b/web/modules/weather_data/src/Service/Test/WeatherAlertParser.php.test index a561b00ce..1300486c8 100644 --- a/web/modules/weather_data/src/Service/Test/WeatherAlertParser.php.test +++ b/web/modules/weather_data/src/Service/Test/WeatherAlertParser.php.test @@ -7,99 +7,121 @@ use PHPUnit\Framework\TestCase; final class WeatherAlertParserTest extends TestCase { - /** - * @group unit - * @group alert-parser - */ - public function testFullWhatWhereWhen(): void - { - $rawDescription = - "* WHAT...Snow expected. Total snow accumulations of 5 to 10\ninches.\n\n"; - $rawDescription .= - "* WHERE...Eastern San Juan Mountains Above 10000 Feet.\n\n"; - $rawDescription .= - "* WHEN...From 11 PM this evening to 11 PM MST Thursday.\n\n"; - $rawDescription .= - "* IMPACTS...Travel could be very difficult." . - " The hazardous conditions may impact travel over Wolf Creek Pass."; - - $expected = [ - [ - "type" => "heading", - "text" => "what", - ], - [ - "type" => "paragraph", - "text" => - "Snow expected. Total snow accumulations of 5 to 10 inches.", - ], - [ - "type" => "heading", - "text" => "where", - ], - [ - "type" => "paragraph", - "text" => "Eastern San Juan Mountains Above 10000 Feet.", - ], - [ - "type" => "heading", - "text" => "when", - ], - [ - "type" => "paragraph", - "text" => "From 11 PM this evening to 11 PM MST Thursday.", - ], - [ - "type" => "heading", - "text" => "impacts", - ], - [ - "type" => "paragraph", - "text" => - "Travel could be very difficult. The hazardous conditions may impact travel over Wolf Creek Pass.", - ], - ]; - - $parser = new WeatherAlertParser($rawDescription); - $parsedDescription = $parser->parse(); - - $this->assertEquals($expected, $parsedDescription); - } - - /** - * @group unit - * @group alert-parser - */ - public function testBasicWhatWhereWhen(): void - { - $rawDescription = - "* WHAT...Snow expected. Total snow accumulations of 5 to 10\ninches.\n\n"; - - $expected = [ - [ - "type" => "heading", - "text" => "what", - ], - [ - "type" => "paragraph", - "text" => - "Snow expected. Total snow accumulations of 5 to 10 inches.", - ], - ]; - - $parser = new WeatherAlertParser($rawDescription); - $parsedDescription = $parser->parse(); - - $this->assertEquals($expected, $parsedDescription); - } - - /** - * @group unit - * @group alert-parser - */ - public function testAlertDescriptionOverviewExample(): void - { - $rawDescription = "...WINTER CONDITIONS RETURN TO THE SIERRA AND NORTHEAST CALIFORNIA + /** + * @group unit + * @group alert-parser + */ + public function testFullWhatWhereWhen(): void + { + $rawDescription = + "* WHAT...Snow expected. Total snow accumulations of 5 to 10\ninches.\n\n"; + $rawDescription .= + "* WHERE...Eastern San Juan Mountains Above 10000 Feet.\n\n"; + $rawDescription .= + "* WHEN...From 11 PM this evening to 11 PM MST Thursday.\n\n"; + $rawDescription .= + "* IMPACTS...Travel could be very difficult." . + " The hazardous conditions may impact travel over Wolf Creek Pass."; + + $expected = [ + [ + "type" => "heading", + "text" => "what", + ], + [ + "type" => "paragraph", + "nodes" => [ + [ + "type" => "text", + "content" => "Snow expected. Total snow accumulations of 5 to 10 inches." + ] + ], + ], + [ + "type" => "heading", + "text" => "where", + ], + [ + "type" => "paragraph", + "nodes" => [ + [ + "type" => "text", + "content" => "Eastern San Juan Mountains Above 10000 Feet." + ] + ], + ], + [ + "type" => "heading", + "text" => "when", + ], + [ + "type" => "paragraph", + "nodes" => [ + [ + "type" => "text", + "content" => "From 11 PM this evening to 11 PM MST Thursday." + ] + ], + ], + [ + "type" => "heading", + "text" => "impacts", + ], + [ + "type" => "paragraph", + "nodes" => [ + [ + "type" => "text", + "content" => "Travel could be very difficult. The hazardous conditions may impact travel over Wolf Creek Pass." + ] + ], + ], + ]; + + $parser = new WeatherAlertParser($rawDescription); + $parsedDescription = $parser->parse(); + + $this->assertEquals($expected, $parsedDescription); + } + + /** + * @group unit + * @group alert-parser + */ + public function testBasicWhatWhereWhen(): void + { + $rawDescription = + "* WHAT...Snow expected. Total snow accumulations of 5 to 10\ninches.\n\n"; + + $expected = [ + [ + "type" => "heading", + "text" => "what", + ], + [ + "type" => "paragraph", + "nodes" => [ + [ + "type" => "text", + "content" => "Snow expected. Total snow accumulations of 5 to 10 inches." + ] + ], + ], + ]; + + $parser = new WeatherAlertParser($rawDescription); + $parsedDescription = $parser->parse(); + + $this->assertEquals($expected, $parsedDescription); + } + + /** + * @group unit + * @group alert-parser + */ + public function testAlertDescriptionOverviewExample(): void + { + $rawDescription = "...WINTER CONDITIONS RETURN TO THE SIERRA AND NORTHEAST CALIFORNIA FOR MID-LATE WEEK... This bit...has ellipses in the middle...but is not an overview. @@ -130,139 +152,287 @@ few power outages may result. * ADDITIONAL DETAILS...Snow levels will start near 6500 feet on Wednesday, then drop to near 5500 feet Wednesday night and near -5000 feet by Thursday morning. -"; +5000 feet by Thursday morning."; - $expected = [ - [ - "type" => "paragraph", - "text" => - "WINTER CONDITIONS RETURN TO THE SIERRA AND NORTHEAST CALIFORNIA FOR MID-LATE WEEK", - ], - [ - "type" => "paragraph", - "text" => - "This bit...has ellipses in the middle...but is not an overview.", - ], - [ - "type" => "paragraph", - "text" => - ".After a few days of warm weather, a potent winter storm" . - " will bring windy and colder conditions with periods of" . - " heavy snow to the Sierra and higher elevations of northeast" . - " California later this week. While weather-related travel impacts" . - " aren't expected through Wednesday morning, conditions will" . - " begin to worsen Wednesday afternoon and evening, with the most" . - " widespread winter travel impacts likely from Wednesday" . - " evening through much of Thursday.", - ], - [ - "type" => "heading", - "text" => "what", - ], - [ - "type" => "paragraph", - "text" => - "Heavy snow possible. Snow accumulations of 4 to 10 inches" . - " above 5000 feet west of US-395, with 10 to 20 inches possible" . - " for higher passes such as Fredonyer Summit and Yuba Pass." . - " Winds gusting as high as 50 mph.", - ], - [ - "type" => "heading", - "text" => "where", - ], - [ - "type" => "paragraph", - "text" => "Lassen-Eastern Plumas-Eastern Sierra Counties.", - ], + $expected = [ + [ + "type" => "paragraph", + "nodes" => + [ [ - "type" => "heading", - "text" => "when", - ], - [ - "type" => "paragraph", - "text" => - "From late Wednesday morning through Friday morning." . - " Heaviest snow is most likely from late Wednesday afternoon through Thursday morning.", - ], - [ - "type" => "heading", - "text" => "impacts", - ], - [ - "type" => "paragraph", - "text" => - "Travel could be very difficult at times," . - " with hazardous conditions impacting the commutes from" . - " Wednesday evening through Friday morning. Strong winds" . - " may blow down some tree limbs and a few power outages" . - " may result.", - ], - [ - "type" => "heading", - "text" => "additional details", - ], - [ - "type" => "paragraph", - "text" => - "Snow levels will start near 6500 feet on Wednesday," . - " then drop to near 5500 feet Wednesday night and near" . - " 5000 feet by Thursday morning.", - ], - ]; - - $parser = new WeatherAlertParser($rawDescription); - $actual = $parser->parse(); - - $this->assertEquals($expected, $actual); - } - - /** - * @group unit - * @group alert-parser - */ - public function testParsesBasicGovUrl() - { - $sourceText = "Scattered showers with brief heavy rainfall will continue this week. + "type" => "text", + "content" => "WINTER CONDITIONS RETURN TO THE SIERRA AND NORTHEAST CALIFORNIA FOR MID-LATE WEEK" + ] + ], + ], + [ + "type" => "paragraph", + "nodes" => [ + [ + "type" => "text", + "content" => "This bit...has ellipses in the middle...but is not an overview." + ] + ], + ], + [ + "type" => "paragraph", + "nodes" => [ + [ + "type" => "text", + "content" => ".After a few days of warm weather, a potent winter storm" . + " will bring windy and colder conditions with periods of" . + " heavy snow to the Sierra and higher elevations of northeast" . + " California later this week. While weather-related travel impacts" . + " aren't expected through Wednesday morning, conditions will" . + " begin to worsen Wednesday afternoon and evening, with the most" . + " widespread winter travel impacts likely from Wednesday" . + " evening through much of Thursday." + ] + ], + ], + [ + "type" => "heading", + "text" => "what", + ], + [ + "type" => "paragraph", + "nodes" => [ + [ + "type" => "text", + "content" => "Heavy snow possible. Snow accumulations of 4 to 10 inches" . + " above 5000 feet west of US-395, with 10 to 20 inches possible" . + " for higher passes such as Fredonyer Summit and Yuba Pass." . + " Winds gusting as high as 50 mph." + ] + ], + ], + [ + "type" => "heading", + "text" => "where", + ], + [ + "type" => "paragraph", + "nodes" => [ + [ + "type" => "text", + "content" => "Lassen-Eastern Plumas-Eastern Sierra Counties." + ] + ], + ], + [ + "type" => "heading", + "text" => "when", + ], + [ + "type" => "paragraph", + "nodes" => [ + [ + "type" => "text", + "content" => "From late Wednesday morning through Friday morning." . + " Heaviest snow is most likely from late Wednesday afternoon through Thursday morning." + ] + ], + ], + [ + "type" => "heading", + "text" => "impacts", + ], + [ + "type" => "paragraph", + "nodes" => [ + [ + "type" => "text", + "content" => "Travel could be very difficult at times," . + " with hazardous conditions impacting the commutes from" . + " Wednesday evening through Friday morning. Strong winds" . + " may blow down some tree limbs and a few power outages" . + " may result." + ] + ], + ], + [ + "type" => "heading", + "text" => "additional details", + ], + [ + "type" => "paragraph", + "nodes" => [ + [ + "type" => "text", + "content" => "Snow levels will start near 6500 feet on Wednesday," . + " then drop to near 5500 feet Wednesday night and near" . + " 5000 feet by Thursday morning." + ] + ], + ], + ]; + + $parser = new WeatherAlertParser($rawDescription); + $actual = $parser->parse(); + + $this->assertEquals($expected, $actual); + } + + /** + * @group unit + * @group alert-parser + */ + public function testParsesBasicGovUrl() + { + $sourceText = "Scattered showers with brief heavy rainfall will continue this week. A low pressure system from the north will batter your area, relentlessly. Be sure to stock up. See https://www.weather.gov/safety/food for more information."; - $parser = new WeatherAlertParser(""); - $actual = $parser->extractURLs($sourceText); - $expectedUrl = "https://www.weather.gov/safety/food"; - - $this->assertEquals(1, count($actual)); - $this->assertEquals($expectedUrl, $actual[0]); - } - - /** - * @group unit - * @group alert-parser - */ - public function testIgnoresNonGovUrls() - { - $sourceText = "Scattered showers with brief heavy rainfall will go on and + $parser = new WeatherAlertParser(""); + $actual = $parser->extractURLs($sourceText); + $expected = [ + "type" => "link", + "url" => "https://www.weather.gov/safety/food", + "external" => false, + "offset" => 168 + ]; + + $this->assertEquals(1, count($actual)); + $this->assertEquals($expected, $actual[0]); + } + + /** + * @group unit + * @group alert-parser + */ + public function testIgnoresNonGovUrls() + { + $sourceText = "Scattered showers with brief heavy rainfall will go on and on and on, boring everyone who has to stay inside. They will attempt to visit sites like https://fake.com/should/not/render which will not be clickable URLs."; - $parser = new WeatherAlertParser(""); - $actual = $parser->extractURLs($sourceText); - - $this->assertEquals(false, $actual); - } - - /** - * @group unit - * @group alert-parser - */ - public function testIgnoresUserPassUrls() - { - $sourceText = "Weather will, once again, be horrendous wherever you happen to be. + $parser = new WeatherAlertParser(""); + $actual = $parser->extractURLs($sourceText); + + $this->assertEquals(false, $actual); + } + + /** + * @group unit + * @group alert-parser + */ + public function testIgnoresUserPassUrls() + { + $sourceText = "Weather will, once again, be horrendous wherever you happen to be. Maybe you are cursed? Anyway, the spammers would like you to click this link here that has a username and password in the URL (https://root:1234@weather.gov/hack/the/gibson), which you shouldn't be able to do thanks to Cautious Public Servants TM."; - $parser = new WeatherAlertParser(""); - $actual = $parser->extractURLs($sourceText); + $parser = new WeatherAlertParser(""); + $actual = $parser->extractURLs($sourceText); + + $this->assertEquals(false, $actual); + } + + /** + * @group unit + * @group alert-parser + */ + public function testAlertUrlNodesInternal() + { + $sourceText = "* WHAT...There will be winds like you cannot believe. See https://winds.weather.gov/info for more information."; + + $expected = [ + [ + "type" => "heading", + "text" => "what" + ], + [ + "type" => "paragraph", + "nodes" => [ + [ + "type" => "text", + "content" => "There will be winds like you cannot believe. See " + ], + [ + "type" => "link", + "url" => "https://winds.weather.gov/info", + "external" => false, + "offset" => 49 + ], + [ + "type" => "text", + "content" => " for more information." + ] + ] + ], + ]; + + $parser = new WeatherAlertParser($sourceText); + $actual = $parser->parse(); + + $this->assertEquals($expected, $actual); + } + + /** + * @group unit + * @group alert-parser + */ + public function testAlertUrlNodesExternal() + { + $sourceText = "* IMPACTS...There will be shaking like you cannot believe. See https://usgs.gov/earthquakes for more information."; + + $expected = [ + [ + "type" => "heading", + "text" => "impacts" + ], + [ + "type" => "paragraph", + "nodes" => [ + [ + "type" => "text", + "content" => "There will be shaking like you cannot believe. See " + ], + [ + "type" => "link", + "url" => "https://usgs.gov/earthquakes", + "external" => true, + "offset" => 51 + ], + [ + "type" => "text", + "content" => " for more information." + ] + ] + ] + ]; + + $parser = new WeatherAlertParser($sourceText); + $actual = $parser->parse(); + + $this->assertEquals($expected, $actual); + } + + /** + * @group unit + * @group alert-parser + */ + public function testAlertUrlNodesInvalid() + { + $sourceText = "* WHEN...There will be weather like you cannot believe. See https://other-weather-site.com for more information, or even check out http://insecure.gov or else www.foo.net"; + + $expected = [ + [ + "type" => "heading", + "text" => "when" + ], + [ + "type" => "paragraph", + "nodes" => [ + [ + "type" => "text", + "content" => "There will be weather like you cannot believe. See https://other-weather-site.com for more information, or even check out http://insecure.gov or else www.foo.net" + ] + ] + ] + ]; + + $parser = new WeatherAlertParser($sourceText); + $actual = $parser->parse(); - $this->assertEquals(false, $actual); - } + $this->assertEquals($expected, $actual); + } } diff --git a/web/modules/weather_data/src/Service/WeatherAlertParser.php b/web/modules/weather_data/src/Service/WeatherAlertParser.php index afc0d4265..6b674eda6 100644 --- a/web/modules/weather_data/src/Service/WeatherAlertParser.php +++ b/web/modules/weather_data/src/Service/WeatherAlertParser.php @@ -58,7 +58,9 @@ public function parse() if (!$parsedOverview && !$parsedWhatWhereWhen) { array_push($this->parsedNodes, [ "type" => "paragraph", - "text" => $paragraph, + "nodes" => $this->getParagraphNodesForString( + $paragraph + ), ]); } } @@ -84,7 +86,9 @@ public function parseOverview($str) if (preg_match($regex, $str, $matches)) { array_push($this->parsedNodes, [ "type" => "paragraph", - "text" => $matches[1], + "nodes" => $this->getParagraphNodesForString( + $matches[1] + ), ]); return true; @@ -116,7 +120,9 @@ public function parseWhatWhereWhen($str) ]); array_push($this->parsedNodes, [ "type" => "paragraph", - "text" => $matches["text"], + "nodes" => $this->getParagraphNodesForString( + $matches["text"] + ), ]); return true; @@ -125,6 +131,52 @@ public function parseWhatWhereWhen($str) return false; } + /** + * Given a string that will be parsed into a paragraph + * node, determine if there are any valid links within it and, + * if so, responds with ordered text and link subnodes + */ + public function getParagraphNodesForString($str) + { + $links = $this->extractURLs($str); + if(!$links){ + return [ + [ + "type" => "text", + "content" => $str + ] + ]; + } + + $nodes = []; + $current = $str; + foreach($links as $link){ + $paraText = substr($current, 0, $link["offset"]); + array_push( + $nodes, + [ + "type" => "text", + "content" => $paraText + ], + $link + ); + + $current = substr($current, $link["offset"] + strlen($link["url"])); + } + + if($current && $current != ""){ + array_push( + $nodes, + [ + "type" => "text", + "content" => $current + ] + ); + } + + return array_values($nodes); + } + /** * Attemps to parse out any valid URLs in the provided * text body. @@ -134,9 +186,9 @@ public function parseWhatWhereWhen($str) public function extractURLs($str) { $regex = "/https\:\/\/[A-Za-z0-9\-._~:\/\?#\[\]@!$]+/"; - if(preg_match_all($regex, $str, $matches)){ + if(preg_match_all($regex, $str, $matches, PREG_OFFSET_CAPTURE)){ $valid = array_filter($matches[0], function($urlString){ - $url = parse_url($urlString); + $url = parse_url($urlString[0]); if(array_key_exists("user", $url)){ return false; } else if(array_key_exists("pass", $url)){ @@ -149,7 +201,23 @@ public function extractURLs($str) if(count($valid) == 0){ return false; } - return array_values($valid); + + // Each link should be an assoc array + // with the URL along with data about + // whether it's internal or external and + // its text offset + return array_map( + function($url){ + $isInternal = str_contains($url[0], "weather.gov"); + return [ + "type" => "link", + "url" => $url[0], + "offset" => $url[1], + "external" => !$isInternal + ]; + }, + array_values($valid) + ); } return false; } From 2d7592316fe9d8c7a265180e5ac8973beda61597 Mon Sep 17 00:00:00 2001 From: eric-gade Date: Tue, 7 May 2024 16:01:30 -0400 Subject: [PATCH 03/11] Updating parser with new testcase and templates --- .../Service/Test/WeatherAlertParser.php.test | 54 ++++++++++++++++--- .../src/Service/WeatherAlertParser.php | 10 ++-- .../block--weathergov-local-alerts.html.twig | 14 ++++- 3 files changed, 65 insertions(+), 13 deletions(-) diff --git a/web/modules/weather_data/src/Service/Test/WeatherAlertParser.php.test b/web/modules/weather_data/src/Service/Test/WeatherAlertParser.php.test index 1300486c8..e9085098a 100644 --- a/web/modules/weather_data/src/Service/Test/WeatherAlertParser.php.test +++ b/web/modules/weather_data/src/Service/Test/WeatherAlertParser.php.test @@ -287,8 +287,7 @@ Be sure to stock up. See https://www.weather.gov/safety/food for more informatio $expected = [ "type" => "link", "url" => "https://www.weather.gov/safety/food", - "external" => false, - "offset" => 168 + "external" => false ]; $this->assertEquals(1, count($actual)); @@ -349,8 +348,7 @@ you shouldn't be able to do thanks to Cautious Public Servants TM."; [ "type" => "link", "url" => "https://winds.weather.gov/info", - "external" => false, - "offset" => 49 + "external" => false ], [ "type" => "text", @@ -389,8 +387,7 @@ you shouldn't be able to do thanks to Cautious Public Servants TM."; [ "type" => "link", "url" => "https://usgs.gov/earthquakes", - "external" => true, - "offset" => 51 + "external" => true ], [ "type" => "text", @@ -435,4 +432,49 @@ you shouldn't be able to do thanks to Cautious Public Servants TM."; $this->assertEquals($expected, $actual); } + + /** + * @group unit + * @group alert-parser + * @group helpme + */ + public function testAlertUrlNodesDoubledRepresentation() + { + $sourceText = "Damaging winds will blow down large objects such as trees and power lines. Power outages are expected. See www.your-power-company.com/outages for more information. Travel will be difficult, especially for high profile vehicles. For road safety, see https://transportation.gov/safe-travels . For more weather information, check out https://weather.gov/your-office for up to date forecasts and alerts."; + + $expected = [ + [ + "type" => "paragraph", + "nodes" => [ + [ + "type" => "text", + "content" => "Damaging winds will blow down large objects such as trees and power lines. Power outages are expected. See www.your-power-company.com/outages for more information. Travel will be difficult, especially for high profile vehicles. For road safety, see " + ], + [ + "type" => "link", + "url" => "https://transportation.gov/safe-travels", + "external" => true + ], + [ + "type" => "text", + "content" => " . For more weather information, check out " + ], + [ + "type" => "link", + "url" => "https://weather.gov/your-office", + "external" => false + ], + [ + "type" => "text", + "content" => " for up to date forecasts and alerts." + ] + ] + ] + ]; + + $parser = new WeatherAlertParser($sourceText); + $actual = $parser->parse(); + + $this->assertEquals($expected, $actual); + } } diff --git a/web/modules/weather_data/src/Service/WeatherAlertParser.php b/web/modules/weather_data/src/Service/WeatherAlertParser.php index 6b674eda6..9801159ce 100644 --- a/web/modules/weather_data/src/Service/WeatherAlertParser.php +++ b/web/modules/weather_data/src/Service/WeatherAlertParser.php @@ -151,7 +151,8 @@ public function getParagraphNodesForString($str) $nodes = []; $current = $str; foreach($links as $link){ - $paraText = substr($current, 0, $link["offset"]); + $pos = strpos($current, $link["url"]); + $paraText = substr($current, 0, $pos); array_push( $nodes, [ @@ -160,8 +161,7 @@ public function getParagraphNodesForString($str) ], $link ); - - $current = substr($current, $link["offset"] + strlen($link["url"])); + $current = substr($current, $pos + strlen($link["url"])); } if($current && $current != ""){ @@ -204,15 +204,13 @@ public function extractURLs($str) // Each link should be an assoc array // with the URL along with data about - // whether it's internal or external and - // its text offset + // whether it's internal or external return array_map( function($url){ $isInternal = str_contains($url[0], "weather.gov"); return [ "type" => "link", "url" => $url[0], - "offset" => $url[1], "external" => !$isInternal ]; }, diff --git a/web/themes/new_weather_theme/templates/block/block--weathergov-local-alerts.html.twig b/web/themes/new_weather_theme/templates/block/block--weathergov-local-alerts.html.twig index 88cf93079..08749df20 100644 --- a/web/themes/new_weather_theme/templates/block/block--weathergov-local-alerts.html.twig +++ b/web/themes/new_weather_theme/templates/block/block--weathergov-local-alerts.html.twig @@ -33,8 +33,20 @@ {% for element in alert.description %} {% if element.type == "heading" %}

{{ element.text | capitalize }}

+ {% elseif element.type == "paragraph" %} +

+ {% for node in element.nodes %} + {% if node.type == "link" %} + + {{node.url}} + + {% else %} + {{ node.content | raw }} + {% endif %} + {% endfor %} +

{% else %} -

{{ element.text }}

+ {{ node }} {% endif %} {% endfor %} From 518b5c8913d419ee6d6fd79f174ccf9a72960efe Mon Sep 17 00:00:00 2001 From: eric-gade Date: Tue, 7 May 2024 16:02:51 -0400 Subject: [PATCH 04/11] Updating alert test data to have links example (high wind warning) --- .../alerts/active__status=actual&area=AR.json | 2 +- .../alerts/active__status=actual&area=AR.json | 291 ++++++------------ 2 files changed, 90 insertions(+), 203 deletions(-) diff --git a/tests/api/data/e2e/alerts/active__status=actual&area=AR.json b/tests/api/data/e2e/alerts/active__status=actual&area=AR.json index c534ba477..fc85f241b 100644 --- a/tests/api/data/e2e/alerts/active__status=actual&area=AR.json +++ b/tests/api/data/e2e/alerts/active__status=actual&area=AR.json @@ -245,7 +245,7 @@ "sender": "w-nws.webmaster@noaa.gov", "senderName": "NWS Los Angeles/Oxnard CA", "headline": "High Wind Warning issued January 3 at 9:50AM PST until January 5 at 1:00AM PST by NWS Los Angeles/Oxnard CA", - "description": "* WHAT...North winds 25 to 35 mph with gusts up to 60 to 65 mph\nexpected.\n\n* WHERE...Santa Ynez Mountains Eastern Range.\n\n* WHEN...Until 1 AM PST Friday.\n\n* IMPACTS...Damaging winds will blow down large objects such as\ntrees and power lines. Power outages are expected. Travel will\nbe difficult, especially for high profile vehicles.", + "description": "* WHAT...North winds 25 to 35 mph with gusts up to 60 to 65 mph\nexpected.\n\n* WHERE...Santa Ynez Mountains Eastern Range.\n\n* WHEN...Until 1 AM PST Friday.\n\n* IMPACTS...Damaging winds will blow down large objects such as\ntrees and power lines. Power outages are expected. See www.your-power-company.com/outages for more information. Travel will\nbe difficult, especially for high profile vehicles. For road safety, see https://transportation.gov/safe-travels . For more weather information, check out https://weather.gov/your-office for up to date forecasts and alerts.", "instruction": "People should avoid being outside in forested areas and around\ntrees and branches. If possible, remain in the lower levels of\nyour home during the windstorm, and avoid windows. Use caution if\nyou must drive.", "response": "Prepare", "parameters": { diff --git a/tests/api/data/testing/alerts/active__status=actual&area=AR.json b/tests/api/data/testing/alerts/active__status=actual&area=AR.json index a38946c02..fc85f241b 100644 --- a/tests/api/data/testing/alerts/active__status=actual&area=AR.json +++ b/tests/api/data/testing/alerts/active__status=actual&area=AR.json @@ -19,68 +19,16 @@ "id": "urn:oid:2.49.0.1.840.0.35c08d67b727d304ea1d6ae50eb6139b4be1cc8f.001.1", "areaDesc": "San Francisco; Marin Coastal Range; Sonoma Coastal Range; North Bay Interior Mountains; Coastal North Bay Including Point Reyes National Seashore; North Bay Interior Valleys; San Francisco Bay Shoreline; San Fransisco Peninsula Coast; East Bay Interior Valleys; Santa Cruz Mountains; Santa Clara Valley Including San Jose; Eastern Santa Clara Hills; East Bay Hills; Southern Salinas Valley/Arroyo Seco and Lake San Antonio; Santa Lucia Mountains and Los Padres National Forest; Mountains Of San Benito County And Interior Monterey County Including Pinnacles National Park; Northern Salinas Valley/Hollister Valley and Carmel Valley; Northern Monterey Bay; Southern Monterey Bay and Big Sur Coast", "geocode": { - "SAME": [ - "006075", - "006041", - "006097", - "006055", - "006001", - "006013", - "006081", - "006085", - "006087", - "006053", - "006069" - ], - "UGC": [ - "CAZ006", - "CAZ502", - "CAZ503", - "CAZ504", - "CAZ505", - "CAZ506", - "CAZ508", - "CAZ509", - "CAZ510", - "CAZ512", - "CAZ513", - "CAZ514", - "CAZ515", - "CAZ516", - "CAZ517", - "CAZ518", - "CAZ528", - "CAZ529", - "CAZ530" - ] + "SAME": [], + "UGC": [] }, - "affectedZones": [ - "https://api.weather.gov/zones/forecast/CAZ006", - "https://api.weather.gov/zones/forecast/CAZ502", - "https://api.weather.gov/zones/forecast/CAZ503", - "https://api.weather.gov/zones/forecast/CAZ504", - "https://api.weather.gov/zones/forecast/CAZ505", - "https://api.weather.gov/zones/forecast/CAZ506", - "https://api.weather.gov/zones/forecast/CAZ508", - "https://api.weather.gov/zones/forecast/CAZ509", - "https://api.weather.gov/zones/forecast/CAZ510", - "https://api.weather.gov/zones/forecast/CAZ512", - "https://api.weather.gov/zones/forecast/CAZ513", - "https://api.weather.gov/zones/forecast/CAZ514", - "https://api.weather.gov/zones/forecast/CAZ515", - "https://api.weather.gov/zones/forecast/CAZ516", - "https://api.weather.gov/zones/forecast/CAZ517", - "https://api.weather.gov/zones/forecast/CAZ518", - "https://api.weather.gov/zones/forecast/CAZ528", - "https://api.weather.gov/zones/forecast/CAZ529", - "https://api.weather.gov/zones/forecast/CAZ530" - ], + "affectedZones": ["https://api.weather.gov/zones/forecast/ARZ045"], "references": [], "sent": "2024-01-29T12:41:00-08:00", - "effective": "2024-01-29T12:41:00-08:00", - "onset": "2024-01-31T04:00:00-08:00", - "expires": "2024-01-30T03:00:00-08:00", - "ends": "2024-02-02T04:00:00-08:00", + "effective": "date:now -1 hour", + "onset": "date:now -1 hour", + "expires": "date:now +1 hour", + "ends": "date:now +1 hour", "status": "Actual", "messageType": "Alert", "category": "Met", @@ -120,13 +68,16 @@ "SAME": ["006035", "006063", "006091"], "UGC": ["CAZ071"] }, - "affectedZones": ["https://api.weather.gov/zones/forecast/CAZ071"], + "affectedZones": [ + "https://api.weather.gov/zones/forecast/CAZ071", + "https://api.weather.gov/zones/fire/ARZ046" + ], "references": [], "sent": "2024-01-29T13:16:00-08:00", - "effective": "2024-01-29T13:16:00-08:00", - "onset": "2024-01-31T10:00:00-08:00", - "expires": "2024-01-30T13:30:00-08:00", - "ends": "2024-02-02T10:00:00-08:00", + "effective": "date:now -1 hour", + "onset": "date:now -1 hour", + "expires": "date:now +1 hour", + "ends": "date:now +1 hour", "status": "Actual", "messageType": "Alert", "category": "Met", @@ -156,134 +107,7 @@ { "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.550760f4ae698a45e239ca34a1ad97353d170175.001.1", "type": "Feature", - "geometry": { - "type": "Polygon", - "coordinates": [ - [ - [-92.275, 34.999], - [-92.26191601093927, 34.998657383688645], - [-92.2488678841831, 34.99763047384207], - [-92.23589138373995, 34.995922085148784], - [-92.22302207729557, 34.99353690018346], - [-92.21029523872437, 34.99048145657227], - [-92.19774575140627, 34.98676412907379], - [-92.18540801261368, 34.9823951066243], - [-92.17331583923105, 34.977386364410656], - [-92.16150237506511, 34.97175163104709], - [-92.15, 34.965506350946114], - [-92.13884024124624, 34.95866764198636], - [-92.12805368692689, 34.95125424859374], - [-92.11766990223755, 34.943286490364244], - [-92.10771734841029, 34.93478620636935], - [-92.09822330470337, 34.92577669529664], - [-92.08921379363066, 34.91628265158972], - [-92.08071350963576, 34.90633009776246], - [-92.07274575140627, 34.89594631307312], - [-92.06533235801365, 34.88515975875376], - [-92.0584936490539, 34.874], - [-92.05224836895292, 34.86249762493489], - [-92.04661363558935, 34.85068416076895], - [-92.0416048933757, 34.83859198738633], - [-92.03723587092622, 34.82625424859374], - [-92.03351854342773, 34.81370476127563], - [-92.03046309981656, 34.80097792270444], - [-92.02807791485122, 34.78810861626006], - [-92.02636952615794, 34.775132115816916], - [-92.02534261631136, 34.76208398906074], - [-92.025, 34.749], - [-92.02534261631136, 34.73591601093926], - [-92.02636952615794, 34.72286788418309], - [-92.02807791485122, 34.709891383739944], - [-92.03046309981656, 34.697022077295564], - [-92.03351854342773, 34.684295238724374], - [-92.03723587092622, 34.67174575140626], - [-92.0416048933757, 34.659408012613675], - [-92.04661363558935, 34.647315839231055], - [-92.05224836895292, 34.635502375065116], - [-92.0584936490539, 34.624], - [-92.06533235801365, 34.612840241246246], - [-92.07274575140627, 34.60205368692689], - [-92.08071350963576, 34.59166990223754], - [-92.08921379363066, 34.58171734841029], - [-92.09822330470337, 34.572223304703364], - [-92.10771734841029, 34.56321379363065], - [-92.11766990223755, 34.55471350963576], - [-92.12805368692689, 34.54674575140626], - [-92.13884024124624, 34.539332358013645], - [-92.15, 34.53249364905389], - [-92.16150237506511, 34.52624836895291], - [-92.17331583923105, 34.52061363558935], - [-92.18540801261368, 34.5156048933757], - [-92.19774575140627, 34.511235870926214], - [-92.21029523872437, 34.507518543427736], - [-92.22302207729557, 34.50446309981655], - [-92.23589138373995, 34.50207791485122], - [-92.2488678841831, 34.50036952615793], - [-92.26191601093927, 34.49934261631136], - [-92.275, 34.499], - [-92.28808398906074, 34.49934261631136], - [-92.30113211581691, 34.50036952615793], - [-92.31410861626006, 34.50207791485122], - [-92.32697792270444, 34.50446309981655], - [-92.33970476127564, 34.507518543427736], - [-92.35225424859374, 34.511235870926214], - [-92.36459198738633, 34.5156048933757], - [-92.37668416076896, 34.52061363558935], - [-92.38849762493489, 34.52624836895291], - [-92.4, 34.53249364905389], - [-92.41115975875377, 34.539332358013645], - [-92.42194631307312, 34.54674575140626], - [-92.43233009776246, 34.55471350963576], - [-92.44228265158972, 34.56321379363065], - [-92.45177669529664, 34.572223304703364], - [-92.46078620636935, 34.58171734841029], - [-92.46928649036425, 34.59166990223754], - [-92.47725424859374, 34.60205368692689], - [-92.48466764198636, 34.612840241246246], - [-92.49150635094611, 34.624], - [-92.4977516310471, 34.635502375065116], - [-92.50338636441066, 34.647315839231055], - [-92.5083951066243, 34.659408012613675], - [-92.51276412907379, 34.67174575140626], - [-92.51648145657228, 34.684295238724374], - [-92.51953690018345, 34.697022077295564], - [-92.52192208514879, 34.709891383739944], - [-92.52363047384208, 34.72286788418309], - [-92.52465738368865, 34.73591601093926], - [-92.525, 34.749], - [-92.52465738368865, 34.76208398906074], - [-92.52363047384208, 34.775132115816916], - [-92.52192208514879, 34.78810861626006], - [-92.51953690018345, 34.80097792270444], - [-92.51648145657228, 34.81370476127563], - [-92.51276412907379, 34.82625424859374], - [-92.5083951066243, 34.83859198738633], - [-92.50338636441066, 34.85068416076895], - [-92.4977516310471, 34.86249762493489], - [-92.49150635094611, 34.874], - [-92.48466764198636, 34.88515975875376], - [-92.47725424859374, 34.89594631307312], - [-92.46928649036425, 34.90633009776246], - [-92.46078620636935, 34.91628265158972], - [-92.45177669529664, 34.92577669529664], - [-92.44228265158972, 34.93478620636935], - [-92.43233009776246, 34.943286490364244], - [-92.42194631307312, 34.95125424859374], - [-92.41115975875377, 34.95866764198636], - [-92.4, 34.965506350946114], - [-92.3884976249349, 34.97175163104709], - [-92.37668416076896, 34.977386364410656], - [-92.36459198738633, 34.9823951066243], - [-92.35225424859374, 34.98676412907379], - [-92.33970476127564, 34.99048145657227], - [-92.32697792270444, 34.99353690018345], - [-92.31410861626006, 34.995922085148784], - [-92.30113211581691, 34.99763047384207], - [-92.28808398906074, 34.998657383688645], - [-92.275, 34.999] - ] - ] - }, + "geometry": null, "properties": { "@id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.550760f4ae698a45e239ca34a1ad97353d170175.001.1", "@type": "wx:Alert", @@ -293,7 +117,7 @@ "SAME": ["005119"], "UGC": [] }, - "affectedZones": [], + "affectedZones": ["https://api.weather.gov/zones/county/ARC120"], "references": [ { "@id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.1afb9e8030b0ed3aac216a52624965e145929bc4.003.1", @@ -345,7 +169,7 @@ "id": "urn:oid:2.49.0.1.840.0.8760a86c78e313ccfc42aa4eb5166572a0e26e9d.003.1", "areaDesc": "Eastern Beaufort Sea Coast", "geocode": { - "SAME": ["005119"], + "SAME": ["005119", "005141"], "UGC": [] }, "affectedZones": [], @@ -357,11 +181,11 @@ "sent": "2024-01-03T02:50:00-09:00" } ], - "sent": "date:now -1 hour", - "effective": "date:now -24 hour", - "onset": "date:now +24 hour", - "expires": "date:now +40 hour", - "ends": "date:now +48 hour", + "sent": "date:today 16:00:-5 -24 hours", + "effective": "date:today 16:00:-5 +24 hours", + "onset": "date:today 16:00:-5 +24 hours", + "expires": "date:today 16:00:-5 +40 hours", + "ends": "date:today 16:00:-5 +48 hours", "status": "Actual", "messageType": "Update", "category": "Met", @@ -421,7 +245,7 @@ "sender": "w-nws.webmaster@noaa.gov", "senderName": "NWS Los Angeles/Oxnard CA", "headline": "High Wind Warning issued January 3 at 9:50AM PST until January 5 at 1:00AM PST by NWS Los Angeles/Oxnard CA", - "description": "* WHAT...North winds 25 to 35 mph with gusts up to 60 to 65 mph\nexpected.\n\n* WHERE...Santa Ynez Mountains Eastern Range.\n\n* WHEN...Until 1 AM PST Friday.\n\n* IMPACTS...Damaging winds will blow down large objects such as\ntrees and power lines. Power outages are expected. Travel will\nbe difficult, especially for high profile vehicles.", + "description": "* WHAT...North winds 25 to 35 mph with gusts up to 60 to 65 mph\nexpected.\n\n* WHERE...Santa Ynez Mountains Eastern Range.\n\n* WHEN...Until 1 AM PST Friday.\n\n* IMPACTS...Damaging winds will blow down large objects such as\ntrees and power lines. Power outages are expected. See www.your-power-company.com/outages for more information. Travel will\nbe difficult, especially for high profile vehicles. For road safety, see https://transportation.gov/safe-travels . For more weather information, check out https://weather.gov/your-office for up to date forecasts and alerts.", "instruction": "People should avoid being outside in forested areas and around\ntrees and branches. If possible, remain in the lower levels of\nyour home during the windstorm, and avoid windows. Use caution if\nyou must drive.", "response": "Prepare", "parameters": { @@ -553,10 +377,10 @@ "id": "urn:oid:2.49.0.1.840.0.edf7c693eacb6f6c34f207d231789f1984baa629.036.2", "areaDesc": "Saint Matthew Island Waters", "geocode": { - "SAME": ["005119"], + "SAME": [], "UGC": [] }, - "affectedZones": [], + "affectedZones": ["https://api.weather.gov/zones/forecast/AR-MARINE"], "references": [], "sent": "date:now -1 hour", "effective": "date:now -1 hour", @@ -755,6 +579,69 @@ ] } } + }, + { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.75fb8046440f816bf90ca0e70a8b3b6e493b7985.001.1", + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-92, 30], + [-90.5, 30], + [-90.5, 30.5], + [-92, 30.5], + [-92, 30] + ] + ] + }, + "properties": { + "@id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.75fb8046440f816bf90ca0e70a8b3b6e493b7985.001.1", + "@type": "wx:Alert", + "id": "urn:oid:2.49.0.1.840.0.75fb8046440f816bf90ca0e70a8b3b6e493b7985.001.1", + "areaDesc": "Appling, GA; Bacon, GA; Wayne, GA", + "geocode": { + "SAME": [], + "UGC": [] + }, + "affectedZones": [], + "references": [], + "sent": "date:now -7 minutes", + "effective": "date:now -7 minutes", + "onset": "date:now -7 minutes", + "expires": "date:now +14 minutes", + "ends": null, + "status": "Actual", + "messageType": "Alert", + "category": "Met", + "severity": "Severe", + "certainty": "Observed", + "urgency": "Immediate", + "event": "Severe Thunderstorm Warning", + "sender": "w-nws.webmaster@noaa.gov", + "senderName": "NWS Jacksonville FL", + "headline": "Severe Thunderstorm Warning issued January 9 at 1:48PM EST until January 9 at 2:15PM EST by NWS Jacksonville FL", + "description": "The National Weather Service in Jacksonville has issued a\n\n* Severe Thunderstorm Warning for...\nSoutheastern Bacon County in southeastern Georgia...\nNorthwestern Wayne County in southeastern Georgia...\nSoutheastern Appling County in southeastern Georgia...\n\n* Until 215 PM EST.\n\n* At 148 PM EST, severe thunderstorms were located along a line\nextending from 7 miles southwest of Glennville to 6 miles northeast\nof Bristol to near Blackshear, moving northeast at 60 mph.\n\nHAZARD...70 mph wind gusts and penny size hail.\n\nSOURCE...Radar indicated.\n\nIMPACT...Expect considerable tree damage. Damage is likely to\nmobile homes, roofs, and outbuildings.\n\n* Locations impacted include...\nSurrency, Odum, and New Lacy.", + "instruction": "Remain alert for a possible tornado! Tornadoes can develop quickly\nfrom severe thunderstorms. If you spot a tornado go at once into the\nbasement or small central room in a sturdy structure.\n\nFor your protection move to an interior room on the lowest floor of a\nbuilding.\n\nIntense thunderstorm lines can produce brief tornadoes and widespread\nsignificant wind damage. Although a tornado is not immediately\nlikely, it is best to move to an interior room on the lowest floor of\na building. These storms may cause serious injury and significant\nproperty damage.\n\nA Tornado Watch remains in effect until 300 PM EST for southeastern\nGeorgia. A Tornado Watch also remains in effect until 600 PM EST for\nsoutheastern Georgia.", + "response": "Shelter", + "parameters": { + "AWIPSidentifier": ["SVRJAX"], + "WMOidentifier": ["WUUS52 KJAX 091848"], + "eventMotionDescription": [ + "2024-01-09T18:48:00-00:00...storm...238DEG...52KT...31.86,-82.01 31.53,-82.17 31.36,-82.3" + ], + "windThreat": ["RADAR INDICATED"], + "maxWindGust": ["70 MPH"], + "hailThreat": ["RADAR INDICATED"], + "maxHailSize": ["0.75"], + "thunderstormDamageThreat": ["CONSIDERABLE"], + "tornadoDetection": ["POSSIBLE"], + "BLOCKCHANNEL": ["EAS", "NWEM", "CMAS"], + "EAS-ORG": ["WXR"], + "VTEC": ["/O.NEW.KJAX.SV.W.0011.240109T1848Z-240109T1915Z/"], + "eventEndingTime": ["2024-01-09T19:15:00+00:00"] + } + } } ], "title": "Current watches, warnings, and advisories for 44.9869 N, 93.2926 W", From c148c19257c1adfa1104cd17a3992e3001465d18 Mon Sep 17 00:00:00 2001 From: eric-gade Date: Wed, 8 May 2024 14:02:22 -0400 Subject: [PATCH 05/11] Initial migration of alerts tests to playwright --- tests/playwright/alerts.spec.js | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 tests/playwright/alerts.spec.js diff --git a/tests/playwright/alerts.spec.js b/tests/playwright/alerts.spec.js new file mode 100644 index 000000000..a37b78342 --- /dev/null +++ b/tests/playwright/alerts.spec.js @@ -0,0 +1,27 @@ +const { test, expect } = require("@playwright/test"); +const { describe, beforeEach } = test; + +describe("Alerts e2e tests", () => { + beforeEach(async ({ page }) => { + await page.goto("http://localhost:8081/play/testing"); + await page.goto("/point/34.749/-92.275"); + }); + + test("The correct number of alerts show on the page", async ({ page }) => { + const alertAccordions = await page.locator("weathergov-alerts div.usa-accordion").all(); + expect(alertAccordions).toHaveLength(7); + }); + + test("All alert accordions are open by default", async ({ page }) => { + const alertAccordions = await page.locator('weathergov-alerts div.usa-accordion button[aria-expanded="true"]').all(); + expect(alertAccordions).toHaveLength(7); + }); + + test("Clicking the alert accordion buttons closes them", async ({ page }) => { + const alertAccordions = page.locator("weathergov-alerts div.usa-accordion button"); + for(let i = 0; i < await alertAccordions.count(); i++){ + await alertAccordions.nth(i).click(); + await expect(alertAccordions.nth(i)).toHaveAttribute("aria-expanded", "false"); + } + }); +}); From 58aecb0363132b9865dfa143c168cef581b0e9f6 Mon Sep 17 00:00:00 2001 From: eric-gade Date: Wed, 8 May 2024 14:36:59 -0400 Subject: [PATCH 06/11] Adding e2e tests for url parsing in alerts --- tests/playwright/alerts.spec.js | 34 +++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/tests/playwright/alerts.spec.js b/tests/playwright/alerts.spec.js index a37b78342..880da9655 100644 --- a/tests/playwright/alerts.spec.js +++ b/tests/playwright/alerts.spec.js @@ -9,12 +9,12 @@ describe("Alerts e2e tests", () => { test("The correct number of alerts show on the page", async ({ page }) => { const alertAccordions = await page.locator("weathergov-alerts div.usa-accordion").all(); - expect(alertAccordions).toHaveLength(7); + expect(alertAccordions).toHaveLength(6); }); test("All alert accordions are open by default", async ({ page }) => { const alertAccordions = await page.locator('weathergov-alerts div.usa-accordion button[aria-expanded="true"]').all(); - expect(alertAccordions).toHaveLength(7); + expect(alertAccordions).toHaveLength(6); }); test("Clicking the alert accordion buttons closes them", async ({ page }) => { @@ -24,4 +24,34 @@ describe("Alerts e2e tests", () => { await expect(alertAccordions.nth(i)).toHaveAttribute("aria-expanded", "false"); } }); + + describe("Parsed URLs in alerts", () => { + test("Should not find a link wrapping the non-gov url", async ({ page }) => { + const containingText = page.locator("weathergov-alerts").getByText("www.your-power-company.com/outages"); + await expect(containingText).toHaveCount(1); + + const link = page.locator('a[href="www.your-power-company.com/outages"]'); + await expect(link).toHaveCount(0); + }); + + test("Should find a link wrapping the external url, and should have the correct class", async ({ page }) => { + const containingText = page.locator("weathergov-alerts").getByText("https://transportation.gov/safe-travels"); + await expect(containingText).toHaveCount(1); + + const link = page.getByRole("link").filter({hasText: "https://transportation.gov/safe-travels"}); + await expect(link).toHaveCount(1); + await expect(link).toHaveAttribute("href", "https://transportation.gov/safe-travels"); + await expect(link).toHaveClass(/usa-link--external/); + }); + + test("Should find a link wrapping the inernal url, and should have the correct class", async ({ page }) => { + const containingText = page.locator("weathergov-alerts").getByText("https://weather.gov/your-office"); + await expect(containingText).toHaveCount(1); + + const link = page.getByRole("link").filter({hasText: "https://weather.gov/your-office"}); + await expect(link).toHaveCount(1); + await expect(link).toHaveAttribute("href", "https://weather.gov/your-office"); + await expect(link).not.toHaveClass(/usa-link--external/); + }); + }); }); From 1361acc7993351866eb421bcd25c6d0c58f03616 Mon Sep 17 00:00:00 2001 From: eric-gade Date: Wed, 8 May 2024 14:43:41 -0400 Subject: [PATCH 07/11] Removing cypress version of alerts test --- tests/e2e/cypress/e2e/alerts.cy.js | 23 ----------------------- 1 file changed, 23 deletions(-) delete mode 100644 tests/e2e/cypress/e2e/alerts.cy.js diff --git a/tests/e2e/cypress/e2e/alerts.cy.js b/tests/e2e/cypress/e2e/alerts.cy.js deleted file mode 100644 index 1897370a2..000000000 --- a/tests/e2e/cypress/e2e/alerts.cy.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint no-unused-expressions: off */ -describe("Alert display testing", () => { - beforeEach(() => { - cy.request("http://localhost:8081/play/testing"); - cy.visit("/point/34.749/-92.275"); - }); - - it("The correct number of alerts show on the page", () => { - cy.get("weathergov-alerts").find("div.usa-accordion").should("have.length", 7); - }); - - it("All alert accordions are open by default", () => { - cy.get("weathergov-alerts > div.usa-accordion button") - .invoke("attr", "aria-expanded") - .should("equal", "true"); - }); - - it("Clicking the alert accordion buttons closes them", () => { - cy.get("weathergov-alerts > div.usa-accordion button").as("accordionButton") - .click({multiple: true}); - cy.get("@accordionButton").invoke("attr", "aria-expanded").should("equal", "false"); - }); -}); From 3cc83b3e43bddbec6964a2db2d7a7f6d9345bd1e Mon Sep 17 00:00:00 2001 From: eric-gade Date: Wed, 8 May 2024 14:59:28 -0400 Subject: [PATCH 08/11] Updating pw e2e location, github ci, and package runner --- .github/workflows/code-standards.yaml | 30 +++++++++++++++++++++++ package.json | 2 ++ tests/playwright/{ => e2e}/alerts.spec.js | 0 3 files changed, 32 insertions(+) rename tests/playwright/{ => e2e}/alerts.spec.js (100%) diff --git a/.github/workflows/code-standards.yaml b/.github/workflows/code-standards.yaml index 13765e552..9baf22e32 100644 --- a/.github/workflows/code-standards.yaml +++ b/.github/workflows/code-standards.yaml @@ -295,6 +295,36 @@ jobs: - name: run tests run: npx playwright test a11y + new-end-to-end-tests: + name: playwright end to end tests + runs-on: ubuntu-latest + needs: [populate-database, should-test] + + steps: + - uses: browser-actions/setup-chrome@v1 + - uses: browser-actions/setup-edge@v1 + + - name: checkout + if: needs.should-test.outputs.yes == 'true' + uses: actions/checkout@v4 + + - name: setup site + if: needs.should-test.outputs.yes == 'true' + uses: ./.github/actions/setup-site + + - uses: actions/setup-node@v4 + with: + node-version: 18 + + - name: install dependencies + run: npm ci + + - name: install browsers + run: npx playwright install --with-deps + + - name: run tests + run: npx playwright test e2e/* + end-to-end-tests: name: end-to-end tests runs-on: ubuntu-latest diff --git a/package.json b/package.json index fa87dfbc7..1635a4931 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,8 @@ "scripts": { "load-spatial": "cd spatial-data && node load-shapefiles.js", "cypress:e2e": "cypress open --project tests/e2e", + "a11y": "playwright test a11y", + "playwright:e2e": "playwright test e2e/*", "js-component-tests": "npx mocha web/themes/**/tests/components/*-tests.js", "js-format": "npx prettier -w 'web/themes/**/assets/**/*.js' 'tests/**/*.js' '*.js'", "js-lint": "eslint 'web/**/assets/**/*.js' 'tests/**/*.js' '*.js'", diff --git a/tests/playwright/alerts.spec.js b/tests/playwright/e2e/alerts.spec.js similarity index 100% rename from tests/playwright/alerts.spec.js rename to tests/playwright/e2e/alerts.spec.js From 226006026bb88e04f057dd7fd70a2f01b37752bb Mon Sep 17 00:00:00 2001 From: eric-gade Date: Wed, 8 May 2024 15:11:14 -0400 Subject: [PATCH 09/11] lints --- tests/playwright/e2e/alerts.spec.js | 2 + .../Service/Test/WeatherAlertParser.php.test | 900 +++++++++--------- .../src/Service/WeatherAlertParser.php | 68 +- 3 files changed, 500 insertions(+), 470 deletions(-) diff --git a/tests/playwright/e2e/alerts.spec.js b/tests/playwright/e2e/alerts.spec.js index 880da9655..74ce85322 100644 --- a/tests/playwright/e2e/alerts.spec.js +++ b/tests/playwright/e2e/alerts.spec.js @@ -1,4 +1,6 @@ +/* eslint-disable no-await-in-loop, no-plusplus */ const { test, expect } = require("@playwright/test"); + const { describe, beforeEach } = test; describe("Alerts e2e tests", () => { diff --git a/web/modules/weather_data/src/Service/Test/WeatherAlertParser.php.test b/web/modules/weather_data/src/Service/Test/WeatherAlertParser.php.test index e9085098a..97a849b3c 100644 --- a/web/modules/weather_data/src/Service/Test/WeatherAlertParser.php.test +++ b/web/modules/weather_data/src/Service/Test/WeatherAlertParser.php.test @@ -7,121 +7,127 @@ use PHPUnit\Framework\TestCase; final class WeatherAlertParserTest extends TestCase { - /** - * @group unit - * @group alert-parser - */ - public function testFullWhatWhereWhen(): void - { - $rawDescription = - "* WHAT...Snow expected. Total snow accumulations of 5 to 10\ninches.\n\n"; - $rawDescription .= - "* WHERE...Eastern San Juan Mountains Above 10000 Feet.\n\n"; - $rawDescription .= - "* WHEN...From 11 PM this evening to 11 PM MST Thursday.\n\n"; - $rawDescription .= - "* IMPACTS...Travel could be very difficult." . - " The hazardous conditions may impact travel over Wolf Creek Pass."; - - $expected = [ - [ - "type" => "heading", - "text" => "what", - ], - [ - "type" => "paragraph", - "nodes" => [ - [ - "type" => "text", - "content" => "Snow expected. Total snow accumulations of 5 to 10 inches." - ] - ], - ], - [ - "type" => "heading", - "text" => "where", - ], - [ - "type" => "paragraph", - "nodes" => [ - [ - "type" => "text", - "content" => "Eastern San Juan Mountains Above 10000 Feet." - ] - ], - ], - [ - "type" => "heading", - "text" => "when", - ], - [ - "type" => "paragraph", - "nodes" => [ - [ - "type" => "text", - "content" => "From 11 PM this evening to 11 PM MST Thursday." - ] - ], - ], - [ - "type" => "heading", - "text" => "impacts", - ], - [ - "type" => "paragraph", - "nodes" => [ - [ - "type" => "text", - "content" => "Travel could be very difficult. The hazardous conditions may impact travel over Wolf Creek Pass." - ] - ], - ], - ]; - - $parser = new WeatherAlertParser($rawDescription); - $parsedDescription = $parser->parse(); - - $this->assertEquals($expected, $parsedDescription); - } - - /** - * @group unit - * @group alert-parser - */ - public function testBasicWhatWhereWhen(): void - { - $rawDescription = - "* WHAT...Snow expected. Total snow accumulations of 5 to 10\ninches.\n\n"; - - $expected = [ - [ - "type" => "heading", - "text" => "what", - ], - [ - "type" => "paragraph", - "nodes" => [ - [ - "type" => "text", - "content" => "Snow expected. Total snow accumulations of 5 to 10 inches." - ] - ], - ], - ]; - - $parser = new WeatherAlertParser($rawDescription); - $parsedDescription = $parser->parse(); - - $this->assertEquals($expected, $parsedDescription); - } - - /** - * @group unit - * @group alert-parser - */ - public function testAlertDescriptionOverviewExample(): void - { - $rawDescription = "...WINTER CONDITIONS RETURN TO THE SIERRA AND NORTHEAST CALIFORNIA + /** + * @group unit + * @group alert-parser + */ + public function testFullWhatWhereWhen(): void + { + $rawDescription = + "* WHAT...Snow expected. Total snow accumulations of 5 to 10\ninches.\n\n"; + $rawDescription .= + "* WHERE...Eastern San Juan Mountains Above 10000 Feet.\n\n"; + $rawDescription .= + "* WHEN...From 11 PM this evening to 11 PM MST Thursday.\n\n"; + $rawDescription .= + "* IMPACTS...Travel could be very difficult." . + " The hazardous conditions may impact travel over Wolf Creek Pass."; + + $expected = [ + [ + "type" => "heading", + "text" => "what", + ], + [ + "type" => "paragraph", + "nodes" => [ + [ + "type" => "text", + "content" => + "Snow expected. Total snow accumulations of 5 to 10 inches.", + ], + ], + ], + [ + "type" => "heading", + "text" => "where", + ], + [ + "type" => "paragraph", + "nodes" => [ + [ + "type" => "text", + "content" => + "Eastern San Juan Mountains Above 10000 Feet.", + ], + ], + ], + [ + "type" => "heading", + "text" => "when", + ], + [ + "type" => "paragraph", + "nodes" => [ + [ + "type" => "text", + "content" => + "From 11 PM this evening to 11 PM MST Thursday.", + ], + ], + ], + [ + "type" => "heading", + "text" => "impacts", + ], + [ + "type" => "paragraph", + "nodes" => [ + [ + "type" => "text", + "content" => + "Travel could be very difficult. The hazardous conditions" . + " may impact travel over Wolf Creek Pass.", + ], + ], + ], + ]; + + $parser = new WeatherAlertParser($rawDescription); + $parsedDescription = $parser->parse(); + + $this->assertEquals($expected, $parsedDescription); + } + + /** + * @group unit + * @group alert-parser + */ + public function testBasicWhatWhereWhen(): void + { + $rawDescription = + "* WHAT...Snow expected. Total snow accumulations of 5 to 10\ninches.\n\n"; + + $expected = [ + [ + "type" => "heading", + "text" => "what", + ], + [ + "type" => "paragraph", + "nodes" => [ + [ + "type" => "text", + "content" => + "Snow expected. Total snow accumulations of 5 to 10 inches.", + ], + ], + ], + ]; + + $parser = new WeatherAlertParser($rawDescription); + $parsedDescription = $parser->parse(); + + $this->assertEquals($expected, $parsedDescription); + } + + /** + * @group unit + * @group alert-parser + */ + public function testAlertDescriptionOverviewExample(): void + { + $rawDescription = "...WINTER CONDITIONS RETURN TO THE SIERRA AND NORTHEAST CALIFORNIA FOR MID-LATE WEEK... This bit...has ellipses in the middle...but is not an overview. @@ -154,327 +160,361 @@ few power outages may result. Wednesday, then drop to near 5500 feet Wednesday night and near 5000 feet by Thursday morning."; - $expected = [ - [ - "type" => "paragraph", - "nodes" => - [ + $expected = [ + [ + "type" => "paragraph", + "nodes" => [ + [ + "type" => "text", + "content" => + "WINTER CONDITIONS RETURN TO THE SIERRA" . + " AND NORTHEAST CALIFORNIA FOR MID-LATE WEEK", + ], + ], + ], + [ + "type" => "paragraph", + "nodes" => [ + [ + "type" => "text", + "content" => + "This bit...has ellipses in the middle...but is not an overview.", + ], + ], + ], + [ + "type" => "paragraph", + "nodes" => [ + [ + "type" => "text", + "content" => + ".After a few days of warm weather, a potent winter storm" . + " will bring windy and colder conditions with periods of" . + " heavy snow to the Sierra and higher elevations of northeast" . + " California later this week. While weather-related travel impacts" . + " aren't expected through Wednesday morning, conditions will" . + " begin to worsen Wednesday afternoon and evening, with the most" . + " widespread winter travel impacts likely from Wednesday" . + " evening through much of Thursday.", + ], + ], + ], + [ + "type" => "heading", + "text" => "what", + ], + [ + "type" => "paragraph", + "nodes" => [ + [ + "type" => "text", + "content" => + "Heavy snow possible. Snow accumulations of 4 to 10 inches" . + " above 5000 feet west of US-395, with 10 to 20 inches possible" . + " for higher passes such as Fredonyer Summit and Yuba Pass." . + " Winds gusting as high as 50 mph.", + ], + ], + ], + [ + "type" => "heading", + "text" => "where", + ], + [ + "type" => "paragraph", + "nodes" => [ + [ + "type" => "text", + "content" => + "Lassen-Eastern Plumas-Eastern Sierra Counties.", + ], + ], + ], [ - "type" => "text", - "content" => "WINTER CONDITIONS RETURN TO THE SIERRA AND NORTHEAST CALIFORNIA FOR MID-LATE WEEK" - ] - ], - ], - [ - "type" => "paragraph", - "nodes" => [ - [ - "type" => "text", - "content" => "This bit...has ellipses in the middle...but is not an overview." - ] - ], - ], - [ - "type" => "paragraph", - "nodes" => [ - [ - "type" => "text", - "content" => ".After a few days of warm weather, a potent winter storm" . - " will bring windy and colder conditions with periods of" . - " heavy snow to the Sierra and higher elevations of northeast" . - " California later this week. While weather-related travel impacts" . - " aren't expected through Wednesday morning, conditions will" . - " begin to worsen Wednesday afternoon and evening, with the most" . - " widespread winter travel impacts likely from Wednesday" . - " evening through much of Thursday." - ] - ], - ], - [ - "type" => "heading", - "text" => "what", - ], - [ - "type" => "paragraph", - "nodes" => [ - [ - "type" => "text", - "content" => "Heavy snow possible. Snow accumulations of 4 to 10 inches" . - " above 5000 feet west of US-395, with 10 to 20 inches possible" . - " for higher passes such as Fredonyer Summit and Yuba Pass." . - " Winds gusting as high as 50 mph." - ] - ], - ], - [ - "type" => "heading", - "text" => "where", - ], - [ - "type" => "paragraph", - "nodes" => [ - [ - "type" => "text", - "content" => "Lassen-Eastern Plumas-Eastern Sierra Counties." - ] - ], - ], - [ - "type" => "heading", - "text" => "when", - ], - [ - "type" => "paragraph", - "nodes" => [ - [ - "type" => "text", - "content" => "From late Wednesday morning through Friday morning." . - " Heaviest snow is most likely from late Wednesday afternoon through Thursday morning." - ] - ], - ], - [ - "type" => "heading", - "text" => "impacts", - ], - [ - "type" => "paragraph", - "nodes" => [ - [ - "type" => "text", - "content" => "Travel could be very difficult at times," . - " with hazardous conditions impacting the commutes from" . - " Wednesday evening through Friday morning. Strong winds" . - " may blow down some tree limbs and a few power outages" . - " may result." - ] - ], - ], - [ - "type" => "heading", - "text" => "additional details", - ], - [ - "type" => "paragraph", - "nodes" => [ - [ - "type" => "text", - "content" => "Snow levels will start near 6500 feet on Wednesday," . - " then drop to near 5500 feet Wednesday night and near" . - " 5000 feet by Thursday morning." - ] - ], - ], - ]; - - $parser = new WeatherAlertParser($rawDescription); - $actual = $parser->parse(); - - $this->assertEquals($expected, $actual); - } - - /** - * @group unit - * @group alert-parser - */ - public function testParsesBasicGovUrl() - { - $sourceText = "Scattered showers with brief heavy rainfall will continue this week. + "type" => "heading", + "text" => "when", + ], + [ + "type" => "paragraph", + "nodes" => [ + [ + "type" => "text", + "content" => + "From late Wednesday morning through Friday morning." . + " Heaviest snow is most likely from late Wednesday afternoon through Thursday morning.", + ], + ], + ], + [ + "type" => "heading", + "text" => "impacts", + ], + [ + "type" => "paragraph", + "nodes" => [ + [ + "type" => "text", + "content" => + "Travel could be very difficult at times," . + " with hazardous conditions impacting the commutes from" . + " Wednesday evening through Friday morning. Strong winds" . + " may blow down some tree limbs and a few power outages" . + " may result.", + ], + ], + ], + [ + "type" => "heading", + "text" => "additional details", + ], + [ + "type" => "paragraph", + "nodes" => [ + [ + "type" => "text", + "content" => + "Snow levels will start near 6500 feet on Wednesday," . + " then drop to near 5500 feet Wednesday night and near" . + " 5000 feet by Thursday morning.", + ], + ], + ], + ]; + + $parser = new WeatherAlertParser($rawDescription); + $actual = $parser->parse(); + + $this->assertEquals($expected, $actual); + } + + /** + * @group unit + * @group alert-parser + */ + public function testParsesBasicGovUrl() + { + $sourceText = "Scattered showers with brief heavy rainfall will continue this week. A low pressure system from the north will batter your area, relentlessly. Be sure to stock up. See https://www.weather.gov/safety/food for more information."; - $parser = new WeatherAlertParser(""); - $actual = $parser->extractURLs($sourceText); - $expected = [ - "type" => "link", - "url" => "https://www.weather.gov/safety/food", - "external" => false - ]; - - $this->assertEquals(1, count($actual)); - $this->assertEquals($expected, $actual[0]); - } - - /** - * @group unit - * @group alert-parser - */ - public function testIgnoresNonGovUrls() - { - $sourceText = "Scattered showers with brief heavy rainfall will go on and + $parser = new WeatherAlertParser(""); + $actual = $parser->extractURLs($sourceText); + $expected = [ + "type" => "link", + "url" => "https://www.weather.gov/safety/food", + "external" => false, + ]; + + $this->assertEquals(1, count($actual)); + $this->assertEquals($expected, $actual[0]); + } + + /** + * @group unit + * @group alert-parser + */ + public function testIgnoresNonGovUrls() + { + $sourceText = "Scattered showers with brief heavy rainfall will go on and on and on, boring everyone who has to stay inside. They will attempt to visit sites like https://fake.com/should/not/render which will not be clickable URLs."; - $parser = new WeatherAlertParser(""); - $actual = $parser->extractURLs($sourceText); - - $this->assertEquals(false, $actual); - } - - /** - * @group unit - * @group alert-parser - */ - public function testIgnoresUserPassUrls() - { - $sourceText = "Weather will, once again, be horrendous wherever you happen to be. + $parser = new WeatherAlertParser(""); + $actual = $parser->extractURLs($sourceText); + + $this->assertEquals(false, $actual); + } + + /** + * @group unit + * @group alert-parser + */ + public function testIgnoresUserPassUrls() + { + $sourceText = "Weather will, once again, be horrendous wherever you happen to be. Maybe you are cursed? Anyway, the spammers would like you to click this link here that has a username and password in the URL (https://root:1234@weather.gov/hack/the/gibson), which you shouldn't be able to do thanks to Cautious Public Servants TM."; - $parser = new WeatherAlertParser(""); - $actual = $parser->extractURLs($sourceText); - - $this->assertEquals(false, $actual); - } - - /** - * @group unit - * @group alert-parser - */ - public function testAlertUrlNodesInternal() - { - $sourceText = "* WHAT...There will be winds like you cannot believe. See https://winds.weather.gov/info for more information."; - - $expected = [ - [ - "type" => "heading", - "text" => "what" - ], - [ - "type" => "paragraph", - "nodes" => [ - [ - "type" => "text", - "content" => "There will be winds like you cannot believe. See " - ], - [ - "type" => "link", - "url" => "https://winds.weather.gov/info", - "external" => false - ], - [ - "type" => "text", - "content" => " for more information." - ] - ] - ], - ]; - - $parser = new WeatherAlertParser($sourceText); - $actual = $parser->parse(); - - $this->assertEquals($expected, $actual); - } - - /** - * @group unit - * @group alert-parser - */ - public function testAlertUrlNodesExternal() - { - $sourceText = "* IMPACTS...There will be shaking like you cannot believe. See https://usgs.gov/earthquakes for more information."; - - $expected = [ - [ - "type" => "heading", - "text" => "impacts" - ], - [ - "type" => "paragraph", - "nodes" => [ - [ - "type" => "text", - "content" => "There will be shaking like you cannot believe. See " - ], - [ - "type" => "link", - "url" => "https://usgs.gov/earthquakes", - "external" => true - ], - [ - "type" => "text", - "content" => " for more information." - ] - ] - ] - ]; - - $parser = new WeatherAlertParser($sourceText); - $actual = $parser->parse(); - - $this->assertEquals($expected, $actual); - } - - /** - * @group unit - * @group alert-parser - */ - public function testAlertUrlNodesInvalid() - { - $sourceText = "* WHEN...There will be weather like you cannot believe. See https://other-weather-site.com for more information, or even check out http://insecure.gov or else www.foo.net"; - - $expected = [ - [ - "type" => "heading", - "text" => "when" - ], - [ - "type" => "paragraph", - "nodes" => [ - [ - "type" => "text", - "content" => "There will be weather like you cannot believe. See https://other-weather-site.com for more information, or even check out http://insecure.gov or else www.foo.net" - ] - ] - ] - ]; - - $parser = new WeatherAlertParser($sourceText); - $actual = $parser->parse(); - - $this->assertEquals($expected, $actual); - } - - /** - * @group unit - * @group alert-parser - * @group helpme - */ - public function testAlertUrlNodesDoubledRepresentation() - { - $sourceText = "Damaging winds will blow down large objects such as trees and power lines. Power outages are expected. See www.your-power-company.com/outages for more information. Travel will be difficult, especially for high profile vehicles. For road safety, see https://transportation.gov/safe-travels . For more weather information, check out https://weather.gov/your-office for up to date forecasts and alerts."; - - $expected = [ - [ - "type" => "paragraph", - "nodes" => [ - [ - "type" => "text", - "content" => "Damaging winds will blow down large objects such as trees and power lines. Power outages are expected. See www.your-power-company.com/outages for more information. Travel will be difficult, especially for high profile vehicles. For road safety, see " - ], - [ - "type" => "link", - "url" => "https://transportation.gov/safe-travels", - "external" => true - ], - [ - "type" => "text", - "content" => " . For more weather information, check out " - ], - [ - "type" => "link", - "url" => "https://weather.gov/your-office", - "external" => false - ], - [ - "type" => "text", - "content" => " for up to date forecasts and alerts." - ] - ] - ] - ]; - - $parser = new WeatherAlertParser($sourceText); - $actual = $parser->parse(); - - $this->assertEquals($expected, $actual); - } + $parser = new WeatherAlertParser(""); + $actual = $parser->extractURLs($sourceText); + + $this->assertEquals(false, $actual); + } + + /** + * @group unit + * @group alert-parser + */ + public function testAlertUrlNodesInternal() + { + $sourceText = + "* WHAT...There will be winds like you cannot believe." . + " See https://winds.weather.gov/info for more information."; + + $expected = [ + [ + "type" => "heading", + "text" => "what", + ], + [ + "type" => "paragraph", + "nodes" => [ + [ + "type" => "text", + "content" => + "There will be winds like you cannot believe. See ", + ], + [ + "type" => "link", + "url" => "https://winds.weather.gov/info", + "external" => false, + ], + [ + "type" => "text", + "content" => " for more information.", + ], + ], + ], + ]; + + $parser = new WeatherAlertParser($sourceText); + $actual = $parser->parse(); + + $this->assertEquals($expected, $actual); + } + + /** + * @group unit + * @group alert-parser + */ + public function testAlertUrlNodesExternal() + { + $sourceText = + "* IMPACTS...There will be shaking like you cannot believe." . + " See https://usgs.gov/earthquakes for more information."; + + $expected = [ + [ + "type" => "heading", + "text" => "impacts", + ], + [ + "type" => "paragraph", + "nodes" => [ + [ + "type" => "text", + "content" => + "There will be shaking like you cannot believe. See ", + ], + [ + "type" => "link", + "url" => "https://usgs.gov/earthquakes", + "external" => true, + ], + [ + "type" => "text", + "content" => " for more information.", + ], + ], + ], + ]; + + $parser = new WeatherAlertParser($sourceText); + $actual = $parser->parse(); + + $this->assertEquals($expected, $actual); + } + + /** + * @group unit + * @group alert-parser + */ + public function testAlertUrlNodesInvalid() + { + $sourceText = + "* WHEN...There will be weather like you cannot believe. " . + "See https://other-weather-site.com for more information, " . + "or even check out http://insecure.gov or else www.foo.net"; + + $expected = [ + [ + "type" => "heading", + "text" => "when", + ], + [ + "type" => "paragraph", + "nodes" => [ + [ + "type" => "text", + "content" => + "There will be weather like you cannot believe. " . + "See https://other-weather-site.com for more " . + "information, or even check " . + "out http://insecure.gov or else www.foo.net", + ], + ], + ], + ]; + + $parser = new WeatherAlertParser($sourceText); + $actual = $parser->parse(); + + $this->assertEquals($expected, $actual); + } + + /** + * @group unit + * @group alert-parser + * @group helpme + */ + public function testAlertUrlNodesDoubledRepresentation() + { + $sourceText = + "Damaging winds will blow down large objects such as trees" . + " and power lines. Power outages are expected. " . + "See www.your-power-company.com/outages for more information. " . + "Travel will be difficult, especially for high profile vehicles. " . + "For road safety, see https://transportation.gov/safe-travels . " . + "For more weather information, check out https://weather.gov/your-office " . + "for up to date forecasts and alerts."; + + $expected = [ + [ + "type" => "paragraph", + "nodes" => [ + [ + "type" => "text", + "content" => + "Damaging winds will blow down large objects such as trees" . + " and power lines. Power outages are expected. " . + "See www.your-power-company.com/outages for more information. " . + "Travel will be difficult, especially for high profile vehicles. " . + "For road safety, see ", + ], + [ + "type" => "link", + "url" => "https://transportation.gov/safe-travels", + "external" => true, + ], + [ + "type" => "text", + "content" => + " . For more weather information, check out ", + ], + [ + "type" => "link", + "url" => "https://weather.gov/your-office", + "external" => false, + ], + [ + "type" => "text", + "content" => " for up to date forecasts and alerts.", + ], + ], + ], + ]; + + $parser = new WeatherAlertParser($sourceText); + $actual = $parser->parse(); + + $this->assertEquals($expected, $actual); + } } diff --git a/web/modules/weather_data/src/Service/WeatherAlertParser.php b/web/modules/weather_data/src/Service/WeatherAlertParser.php index 9801159ce..821bba7a8 100644 --- a/web/modules/weather_data/src/Service/WeatherAlertParser.php +++ b/web/modules/weather_data/src/Service/WeatherAlertParser.php @@ -58,9 +58,7 @@ public function parse() if (!$parsedOverview && !$parsedWhatWhereWhen) { array_push($this->parsedNodes, [ "type" => "paragraph", - "nodes" => $this->getParagraphNodesForString( - $paragraph - ), + "nodes" => $this->getParagraphNodesForString($paragraph), ]); } } @@ -86,9 +84,7 @@ public function parseOverview($str) if (preg_match($regex, $str, $matches)) { array_push($this->parsedNodes, [ "type" => "paragraph", - "nodes" => $this->getParagraphNodesForString( - $matches[1] - ), + "nodes" => $this->getParagraphNodesForString($matches[1]), ]); return true; @@ -120,9 +116,7 @@ public function parseWhatWhereWhen($str) ]); array_push($this->parsedNodes, [ "type" => "paragraph", - "nodes" => $this->getParagraphNodesForString( - $matches["text"] - ), + "nodes" => $this->getParagraphNodesForString($matches["text"]), ]); return true; @@ -139,39 +133,36 @@ public function parseWhatWhereWhen($str) public function getParagraphNodesForString($str) { $links = $this->extractURLs($str); - if(!$links){ + if (!$links) { return [ [ "type" => "text", - "content" => $str - ] + "content" => $str, + ], ]; } $nodes = []; $current = $str; - foreach($links as $link){ + foreach ($links as $link) { $pos = strpos($current, $link["url"]); $paraText = substr($current, 0, $pos); array_push( $nodes, [ "type" => "text", - "content" => $paraText + "content" => $paraText, ], - $link + $link, ); $current = substr($current, $pos + strlen($link["url"])); } - if($current && $current != ""){ - array_push( - $nodes, - [ - "type" => "text", - "content" => $current - ] - ); + if ($current && $current != "") { + array_push($nodes, [ + "type" => "text", + "content" => $current, + ]); } return array_values($nodes); @@ -186,36 +177,33 @@ public function getParagraphNodesForString($str) public function extractURLs($str) { $regex = "/https\:\/\/[A-Za-z0-9\-._~:\/\?#\[\]@!$]+/"; - if(preg_match_all($regex, $str, $matches, PREG_OFFSET_CAPTURE)){ - $valid = array_filter($matches[0], function($urlString){ + if (preg_match_all($regex, $str, $matches, PREG_OFFSET_CAPTURE)) { + $valid = array_filter($matches[0], function ($urlString) { $url = parse_url($urlString[0]); - if(array_key_exists("user", $url)){ + if (array_key_exists("user", $url)) { return false; - } else if(array_key_exists("pass", $url)){ + } elseif (array_key_exists("pass", $url)) { return false; - } else if(!str_ends_with($url["host"], ".gov")){ + } elseif (!str_ends_with($url["host"], ".gov")) { return false; } return true; }); - if(count($valid) == 0){ + if (count($valid) == 0) { return false; } // Each link should be an assoc array // with the URL along with data about // whether it's internal or external - return array_map( - function($url){ - $isInternal = str_contains($url[0], "weather.gov"); - return [ - "type" => "link", - "url" => $url[0], - "external" => !$isInternal - ]; - }, - array_values($valid) - ); + return array_map(function ($url) { + $isInternal = str_contains($url[0], "weather.gov"); + return [ + "type" => "link", + "url" => $url[0], + "external" => !$isInternal, + ]; + }, array_values($valid)); } return false; } From e1e40f9bf6434f54c134592dafa2f09e4f7bcf8f Mon Sep 17 00:00:00 2001 From: Eric Gade <105373963+eric-gade@users.noreply.github.com> Date: Wed, 8 May 2024 21:09:17 -0400 Subject: [PATCH 10/11] Update web/modules/weather_data/src/Service/WeatherAlertParser.php Co-authored-by: Greg Walker <142943695+greg-does-weather@users.noreply.github.com> --- web/modules/weather_data/src/Service/WeatherAlertParser.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/modules/weather_data/src/Service/WeatherAlertParser.php b/web/modules/weather_data/src/Service/WeatherAlertParser.php index 821bba7a8..8070e1799 100644 --- a/web/modules/weather_data/src/Service/WeatherAlertParser.php +++ b/web/modules/weather_data/src/Service/WeatherAlertParser.php @@ -176,7 +176,7 @@ public function getParagraphNodesForString($str) */ public function extractURLs($str) { - $regex = "/https\:\/\/[A-Za-z0-9\-._~:\/\?#\[\]@!$]+/"; + $regex = "/https\:\/\/[A-Za-z0-9\-._~:\/\?#\[\]@!$]+\b/"; if (preg_match_all($regex, $str, $matches, PREG_OFFSET_CAPTURE)) { $valid = array_filter($matches[0], function ($urlString) { $url = parse_url($urlString[0]); From e3212928f823833f7e1b141ee37174ed0259df03 Mon Sep 17 00:00:00 2001 From: eric-gade Date: Thu, 9 May 2024 09:29:04 -0400 Subject: [PATCH 11/11] Updating url host parsing and template --- web/modules/weather_data/src/Service/WeatherAlertParser.php | 4 +++- .../templates/block/block--weathergov-local-alerts.html.twig | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/web/modules/weather_data/src/Service/WeatherAlertParser.php b/web/modules/weather_data/src/Service/WeatherAlertParser.php index 8070e1799..2c15720a5 100644 --- a/web/modules/weather_data/src/Service/WeatherAlertParser.php +++ b/web/modules/weather_data/src/Service/WeatherAlertParser.php @@ -189,6 +189,7 @@ public function extractURLs($str) } return true; }); + if (count($valid) == 0) { return false; } @@ -197,7 +198,8 @@ public function extractURLs($str) // with the URL along with data about // whether it's internal or external return array_map(function ($url) { - $isInternal = str_contains($url[0], "weather.gov"); + $parsedUrl = parse_url($url[0]); + $isInternal = str_contains($parsedUrl["host"], "weather.gov"); return [ "type" => "link", "url" => $url[0], diff --git a/web/themes/new_weather_theme/templates/block/block--weathergov-local-alerts.html.twig b/web/themes/new_weather_theme/templates/block/block--weathergov-local-alerts.html.twig index 08749df20..081dd3ecf 100644 --- a/web/themes/new_weather_theme/templates/block/block--weathergov-local-alerts.html.twig +++ b/web/themes/new_weather_theme/templates/block/block--weathergov-local-alerts.html.twig @@ -41,7 +41,7 @@ {{node.url}} {% else %} - {{ node.content | raw }} + {{ node.content }} {% endif %} {% endfor %}