test: melhorar cobertura e migrar atualizações de GitHub Actions#76
Conversation
- Atualiza actions/checkout v6 -> v7, actions/cache v5 -> v6, codecov/codecov-action v6 -> v7, JetBrains/qodana-action v2026.1.0 -> v2026.1.3 - Refatora DatetimeChunkDecoder (METAR/TAF) para aceitar data de referência, tornando a lógica de rollover testável - Adiciona testes de rollover de mês/ano e clamping de dias - Adiciona testes de RVR com tendência 'N' - Adiciona testes de serialização para MetarChunkDecoderException e TafChunkDecoderException - Adiciona testes para InstantiateEntity do EvolutionChunkDecoder - Cobertura global: 99.5% (METAR 100%, TAF 99%) Co-Authored-By: Afonso Dutra Nogueira Filho <[email protected]>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
Atualiza o argumento --linter de qodana-community-for-net para qodana-cdnet, compatível com a versão 2026.1.3 da action. Co-Authored-By: Afonso Dutra Nogueira Filho <[email protected]>
| public DatetimeChunkDecoder() : this(DateTime.UtcNow) | ||
| { |
There was a problem hiding this comment.
🔴 Date rollover uses a frozen timestamp instead of the current time, producing wrong dates in long-running applications
The reference date for month/year rollover is captured once at construction time (new DatetimeChunkDecoder() at src/Metar.Decoder/MetarDecoder.cs:32) and stored in a static readonly decoder chain, so every subsequent parse reuses the date from when the class was first loaded.
Impact: In any application that runs past midnight or across month boundaries, METAR observations will be assigned to the wrong month or year.
Mechanism: static field freezes DateTime.UtcNow at class-load time
Previously, DateTime.UtcNow was called directly inside the Parse method each time a METAR was decoded, so the rollover logic always used the current date. The PR refactored this so the reference date is stored in _referenceDate, which is set via the default constructor DatetimeChunkDecoder() : this(DateTime.UtcNow) at src/Metar.Decoder/ChunkDecoder/DatetimeChunkDecoder.cs:17.
However, the decoder chain in MetarDecoder is declared as private static readonly at src/Metar.Decoder/MetarDecoder.cs:28, meaning the DatetimeChunkDecoder instance (and its _referenceDate) is created exactly once when the static field initializer runs. All calls to MetarDecoder.ParseWithMode() reuse this same instance.
For example, if the application starts on July 9 and a METAR is parsed on July 10 with day=10, the rollover check day > _referenceDate.Day compares 10 > 9 (true), incorrectly rolling back to June. With the old code, it would compare 10 > 10 (false) and correctly stay in July.
The same issue exists in src/Taf.Decoder/TafDecoder.cs:32.
Prompt for agents
The DatetimeChunkDecoder captures DateTime.UtcNow at construction time in _referenceDate, but the instance lives in a static readonly decoder chain (MetarDecoder._decoderChain at MetarDecoder.cs:28-45 and TafDecoder._decoderChain at TafDecoder.cs:28-39). This means the reference date is frozen at class-load time.
The previous code called DateTime.UtcNow directly inside the Parse method, so it always used the current time. The refactoring broke this by moving the UtcNow call to construction time.
Possible fixes:
1. In BuildObservationDateTime/BuildOriginDateTime, use DateTime.UtcNow directly instead of _referenceDate when _referenceDate was set via the default constructor. For example, store a nullable _referenceDate and fall back to DateTime.UtcNow when null.
2. Change the default parameterless constructor to not set _referenceDate, and have BuildObservationDateTime use DateTime.UtcNow as the default when _referenceDate is not explicitly provided.
3. Make the decoder chain non-static so a fresh DatetimeChunkDecoder is created per MetarDecoder/TafDecoder instance.
The parameterized constructor (for testing) should continue to use the provided referenceDate. Both src/Metar.Decoder/ChunkDecoder/DatetimeChunkDecoder.cs and src/Taf.Decoder/ChunkDecoder/DatetimeChunkDecoder.cs need the same fix.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Corrigido. O construtor padrão não congela mais DateTime.UtcNow; _referenceDate é DateTime? e DateTimeHelper.BuildDateTime usa DateTime.UtcNow apenas quando nenhuma data de referência é fornecida. A cadeia estática de decoders reutiliza a mesma instância, mas a data de referência é avaliada no momento do parse.
| public DatetimeChunkDecoder() : this(DateTime.UtcNow) | ||
| { |
There was a problem hiding this comment.
🔴 Date rollover uses a frozen timestamp instead of the current time, producing wrong dates in long-running applications (TAF decoder)
The reference date for month/year rollover is captured once at construction time (new DatetimeChunkDecoder() at src/Taf.Decoder/TafDecoder.cs:32) and stored in a static readonly decoder chain, so every subsequent parse reuses the date from when the class was first loaded.
Impact: In any application that runs past midnight or across month boundaries, TAF forecasts will be assigned to the wrong month or year.
Mechanism: static field freezes DateTime.UtcNow at class-load time
This is the same issue as in the METAR decoder. The DatetimeChunkDecoder default constructor at src/Taf.Decoder/ChunkDecoder/DatetimeChunkDecoder.cs:17 captures DateTime.UtcNow into _referenceDate. The decoder chain at src/Taf.Decoder/TafDecoder.cs:28 is static readonly, so the instance is created once and reused for all TAF parsing. The old code called DateTime.UtcNow directly in the Parse method.
Prompt for agents
Same fix needed as for the METAR DatetimeChunkDecoder. The default constructor should not freeze DateTime.UtcNow into _referenceDate when the instance will live in a static readonly decoder chain. See the METAR decoder bug for suggested approaches.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Corrigido com o mesmo ajuste do METAR: a data de referência não é mais capturada no construtor padrão, evitando que static readonly decoders congelem a data no class-load.
| public void GetObjectDataSerializesRemainingMetarFields() | ||
| { | ||
| var decoder = new IcaoChunkDecoder(); | ||
| var dto = new MetarChunkDecoderException("REMAINING", "NEW", "Test", decoder); | ||
| var info = new SerializationInfo(typeof(MetarChunkDecoderException), new FormatterConverter()); | ||
|
|
||
| dto.GetObjectData(info, new StreamingContext()); | ||
|
|
||
| ClassicAssert.That(info.GetString("RemainingMetar"), Is.EqualTo("NEW")); | ||
| ClassicAssert.That(info.GetString("NewRemainingMetar"), Is.EqualTo("REMAINING")); | ||
| } |
There was a problem hiding this comment.
🔍 Serialization tests codify a pre-existing field-name swap in GetObjectData
The new test GetObjectDataSerializesRemainingMetarFields at tests/Metar.Decoder.Tests/MetarChunkDecoderExceptionTest.cs:67-77 asserts that info.GetString("RemainingMetar") equals "NEW" and info.GetString("NewRemainingMetar") equals "REMAINING". This matches the production code at src/Metar.Decoder/Exception/MetarChunkDecoderException.cs:68-69 where GetObjectData stores RemainingMetar under key "NewRemainingMetar" and vice versa. The same swap exists in src/Taf.Decoder/Exception/TafChunkDecoderException.cs:45-46 and is tested at tests/Taf.Decoder.Tests/Entity/TafExceptionExtendedTest.cs:70-71. While a round-trip (serialize → deserialize) happens to work because the deserialization constructor reads the same swapped keys, the serialized representation has semantically incorrect field names. These new tests lock in this behavior, making it harder to fix later.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Você está correto: o GetObjectData inverte os nomes RemainingMetar e NewRemainingMetar no dicionário de serialização. Isso é um comportamento preexistente e, embora a round-trip funcione porque o construtor de deserialização lê as mesmas chaves, trocar os nomes agora quebraria a compatibilidade binária de qualquer serialização anterior. Mantive os testes apenas para documentar/cobrir o comportamento atual, sem alterar a implementação. Se desejar corrigir isso em uma mudança breaking futura, posso abrir uma issue separada.
| if (day > _referenceDate.Day) | ||
| { | ||
| if (month == 1) | ||
| { |
There was a problem hiding this comment.
📝 Info: Rollover logic does not account for same-day observations from the current month
The rollover condition if (day > _referenceDate.Day) at src/Metar.Decoder/ChunkDecoder/DatetimeChunkDecoder.cs:81 (and the TAF equivalent at src/Taf.Decoder/ChunkDecoder/DatetimeChunkDecoder.cs:80) means that if the METAR day equals the reference day, no rollover occurs — the observation is assumed to be in the current month. However, if a METAR is issued on day 31 and the reference date is also day 31 but in a different month (e.g., reference is March 31 and the METAR is from January 31), the logic will incorrectly assign it to March. This is an inherent limitation of METAR/TAF formats which only include the day, not the month. The previous code had the same limitation, so this is not a regression, but it's worth noting for anyone relying on the ObservationDateTime/OriginDateTime field for historical data.
(Refers to lines 81-92)
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Concordo. Essa limitação é inerente ao formato METAR/TAF, que só traz o dia do mês. O mesmo comportamento existia no código anterior. A lógica continua útil para a maioria dos casos (relatórios recentes), mas para dados históricos o consumidor deve fornecer a data de referência correta via construtor DatetimeChunkDecoder(referenceDate).
| { | ||
| day = daysInMonth; | ||
| } |
There was a problem hiding this comment.
🔍 Day clamping silently alters the parsed day value without warning
When the rollover logic resolves to a month with fewer days than the parsed day (e.g., day 31 rolling back to a 30-day month), the code at src/Metar.Decoder/ChunkDecoder/DatetimeChunkDecoder.cs:96-99 silently clamps the day to daysInMonth. This means the ObservationDateTime will have a different day than what the Day field reports (the Day result key is set before clamping at line 58, using the original parsed value). For example, parsing day 31 with a reference date of May 1 would yield Day=31 but ObservationDateTime with day 30 (April). This inconsistency between the two result fields could confuse consumers.
(Refers to lines 96-99)
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Você tem razão: o clamping silencioso cria inconsistência entre Day (valor original) e ObservationDateTime/OriginDateTime (valor ajustado). No entanto, isso preserva o mesmo comportamento do código anterior e evita ArgumentOutOfRangeException ao construir DateTime para meses com menos dias. Alterar para lançar exceção ou ajustar Day também seria uma mudança de comportamento. Deixei como está para manter compatibilidade, com os testes documentando o clamping.
O DatetimeChunkDecoder padrão estava capturando DateTime.UtcNow no construtor, mas a cadeia de decoders é static readonly, então a data ficava congelada desde o primeiro uso. Agora o construtor padrão não recebe data, e BuildObservationDateTime/BuildOriginDateTime usam DateTime.UtcNow a cada chamada quando nenhuma data de referência for explicitamente fornecida. O construtor com parâmetro continua determinístico para testes. Co-Authored-By: Afonso Dutra Nogueira Filho <[email protected]>
O linter qodana-cdnet exige que o caminho da solution seja informado via --solution quando não há qodana.yaml. Co-Authored-By: Afonso Dutra Nogueira Filho <[email protected]>
Evita falha do workflow de security-scan quando SNYK_TOKEN não está configurado, seguindo o mesmo padrão do SonarQube. Co-Authored-By: Afonso Dutra Nogueira Filho <[email protected]>
Qodana Community for .NETIt seems all right 👌 No new problems were found according to the checks applied 💡 Qodana analysis was run in the pull request mode: only the changed files were checked View the detailed Qodana reportTo be able to view the detailed Qodana report, you can either:
To get - name: 'Qodana Scan'
uses: JetBrains/[email protected]
with:
upload-result: trueContact Qodana teamContact us at [email protected]
|
Gera o arquivo qodana.sarif.json para ser usado como baseline pelo Qodana Community for .NET, evitando que problemas preexistentes sejam reportados como novos em PRs. Co-Authored-By: Afonso Dutra Nogueira Filho <[email protected]>
O workflow security-scan agora passa SNYK_TOKEN via env e verifica a variável de ambiente, em vez de expandir o secret diretamente no script run, atendendo à regra do SonarCloud. Co-Authored-By: Afonso Dutra Nogueira Filho <[email protected]>
…hado Reduz duplicação entre os decoders METAR e TAF movendo a lógica de BuildObservationDateTime/BuildOriginDateTime para DateTimeHelper em src/Shared, compilado via Link em ambos os projetos. Co-Authored-By: Afonso Dutra Nogueira Filho <[email protected]>
Adiciona asserções de null e ContainsKey nos testes de DatetimeChunkDecoder para evitar warnings de 'dereferenced variable may be null'. Renomeia variável local em EvolutionChunkDecoderTest para evitar shadowing do campo da classe. Co-Authored-By: Afonso Dutra Nogueira Filho <[email protected]>
|
|
|
||
| var result = decoded[MetarDecoder.ResultKey] as Dictionary<string, object>; | ||
| Assert.That(result, Is.Not.Null); | ||
| Assert.That(result.ContainsKey(DatetimeChunkDecoder.ObservationDateTimeParameterName), Is.True); |
|
|
||
| var result = decoded[MetarDecoder.ResultKey] as Dictionary<string, object>; | ||
| Assert.That(result, Is.Not.Null); | ||
| Assert.That(result.ContainsKey(DatetimeChunkDecoder.ObservationDateTimeParameterName), Is.True); |
|
|
||
| var result = decoded[MetarDecoder.ResultKey] as Dictionary<string, object>; | ||
| Assert.That(result, Is.Not.Null); | ||
| Assert.That(result.ContainsKey(DatetimeChunkDecoder.ObservationDateTimeParameterName), Is.True); |
|
|
||
| var result = decoded[TafDecoder.ResultKey] as Dictionary<string, object>; | ||
| Assert.That(result, Is.Not.Null); | ||
| Assert.That(result.ContainsKey(DatetimeChunkDecoder.OriginDateTimeParameterName), Is.True); |
|
|
||
| var result = decoded[TafDecoder.ResultKey] as Dictionary<string, object>; | ||
| Assert.That(result, Is.Not.Null); | ||
| Assert.That(result.ContainsKey(DatetimeChunkDecoder.OriginDateTimeParameterName), Is.True); |
|
|
||
| var result = decoded[TafDecoder.ResultKey] as Dictionary<string, object>; | ||
| Assert.That(result, Is.Not.Null); | ||
| Assert.That(result.ContainsKey(DatetimeChunkDecoder.OriginDateTimeParameterName), Is.True); |



All Submissions:
New Feature Submissions:
Changes to Core Features:
Summary
This PR consolidates the currently open Dependabot updates for GitHub Actions, fixes the Qodana/Snyk/SonarCloud pipelines, and improves the test suite for
afonsoft/metar-decoder.GitHub Actions updates
actions/checkout:v6→v7across all workflowsactions/cache:v5→v6inci-build-test.ymlandopenhands-resolver.ymlcodecov/codecov-action:v6→v7inci-build-test.ymlJetBrains/qodana-action:v2026.1.0→v2026.1.3incode-quality.ymlCI pipeline fixes
qodana-community-for-nettoqodana-cdnet, added--solution MetarDecoder.sln, and committedqodana.sarif.jsonas baseline.SNYK_TOKENis not configured, avoiding 401 failures.security-scan.ymland reduced code duplication by extracting sharedDateTimerollover logic.Code improvements
referenceDate, making month/year rollover and day-clamping logic deterministic and testable. The sharedDateTimeHelper.BuildDateTimehelper removes duplication between METAR and TAF while keeping the default constructor safe for long-running apps (usesDateTime.UtcNowlazily).Test improvements
N(no change).MetarChunkDecoderExceptionandTafChunkDecoderExceptioncoveringGetObjectDataand the protectedSerializationInfoconstructor.Parse(NotImplementedException) andInstantiateEntityunknown entity path.Coverage
net8.0andnet10.0Link to Devin session: https://app.devin.ai/sessions/eba0afc252f2406f99f725e0397145db
Requested by: @afonsoft